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.Color; 27 import android.graphics.drawable.ColorDrawable; 28 import android.util.AttributeSet; 29 import android.util.MathUtils; 30 import android.view.MotionEvent; 31 import android.view.VelocityTracker; 32 import android.view.View; 33 import android.view.ViewTreeObserver; 34 import android.view.accessibility.AccessibilityEvent; 35 import android.view.animation.AnimationUtils; 36 import android.view.animation.Interpolator; 37 import android.widget.FrameLayout; 38 import android.widget.TextView; 39 40 import com.android.keyguard.KeyguardStatusView; 41 import com.android.systemui.R; 42 import com.android.systemui.qs.QSPanel; 43 import com.android.systemui.statusbar.ExpandableView; 44 import com.android.systemui.statusbar.FlingAnimationUtils; 45 import com.android.systemui.statusbar.GestureRecorder; 46 import com.android.systemui.statusbar.KeyguardAffordanceView; 47 import com.android.systemui.statusbar.StatusBarState; 48 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 49 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 50 import com.android.systemui.statusbar.stack.StackStateAnimator; 51 52 public 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 mKeyguardStatusViewAnimating = false; 987 mKeyguardStatusView.setVisibility(View.VISIBLE); 988 mKeyguardStatusView.setAlpha(1f); 989 } else { 990 mKeyguardStatusView.animate().cancel(); 991 mKeyguardStatusViewAnimating = false; 992 mKeyguardStatusView.setVisibility(View.GONE); 993 mKeyguardStatusView.setAlpha(1f); 994 } 995 } 996 997 private void updateQsState() { 998 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 999 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE); 1000 mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling)); 1001 mNotificationStackScroller.setScrollingEnabled( 1002 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 1003 || mQsExpansionFromOverscroll)); 1004 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 1005 mQsContainer.setVisibility( 1006 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 1007 mScrollView.setTouchEnabled(mQsExpanded); 1008 updateEmptyShadeView(); 1009 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 1010 && !mStackScrollerOverscrolling && mQsScrimEnabled 1011 ? View.VISIBLE 1012 : View.INVISIBLE); 1013 if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { 1014 mKeyguardUserSwitcher.hide(true /* animate */); 1015 } 1016 } 1017 1018 private void setQsExpansion(float height) { 1019 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 1020 mQsFullyExpanded = height == mQsMaxExpansionHeight; 1021 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 1022 setQsExpanded(true); 1023 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 1024 setQsExpanded(false); 1025 if (mLastAnnouncementWasQuickSettings && !mTracking) { 1026 announceForAccessibility(getKeyguardOrLockScreenString()); 1027 mLastAnnouncementWasQuickSettings = false; 1028 } 1029 } 1030 mQsExpansionHeight = height; 1031 mHeader.setExpansion(getHeaderExpansionFraction()); 1032 setQsTranslation(height); 1033 requestScrollerTopPaddingUpdate(false /* animate */); 1034 updateNotificationScrim(height); 1035 if (mKeyguardShowing) { 1036 updateHeaderKeyguard(); 1037 } 1038 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 1039 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 1040 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 1041 } 1042 1043 // Upon initialisation when we are not layouted yet we don't want to announce that we are 1044 // fully expanded, hence the != 0.0f check. 1045 if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { 1046 announceForAccessibility(getContext().getString( 1047 R.string.accessibility_desc_quick_settings)); 1048 mLastAnnouncementWasQuickSettings = true; 1049 } 1050 } 1051 1052 private String getKeyguardOrLockScreenString() { 1053 if (mStatusBarState == StatusBarState.KEYGUARD) { 1054 return getContext().getString(R.string.accessibility_desc_lock_screen); 1055 } else { 1056 return getContext().getString(R.string.accessibility_desc_notification_shade); 1057 } 1058 } 1059 1060 private void updateNotificationScrim(float height) { 1061 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 1062 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 1063 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 1064 } 1065 1066 private float getHeaderExpansionFraction() { 1067 if (!mKeyguardShowing) { 1068 return getQsExpansionFraction(); 1069 } else { 1070 return 1f; 1071 } 1072 } 1073 1074 private void setQsTranslation(float height) { 1075 if (!mHeaderAnimatingIn) { 1076 mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation()); 1077 } 1078 if (mKeyguardShowing) { 1079 mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); 1080 } 1081 } 1082 1083 private float calculateQsTopPadding() { 1084 // We can only do the smoother transition on Keyguard when we also are not collapsing from a 1085 // scrolled quick settings. 1086 if (mKeyguardShowing && mScrollYOverride == -1) { 1087 return interpolate(getQsExpansionFraction(), 1088 mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding, 1089 mQsMaxExpansionHeight); 1090 } else { 1091 return mQsExpansionHeight; 1092 } 1093 } 1094 1095 private void requestScrollerTopPaddingUpdate(boolean animate) { 1096 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 1097 mScrollView.getScrollY(), 1098 mAnimateNextTopPaddingChange || animate); 1099 mAnimateNextTopPaddingChange = false; 1100 } 1101 1102 private void trackMovement(MotionEvent event) { 1103 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 1104 mLastTouchX = event.getX(); 1105 mLastTouchY = event.getY(); 1106 } 1107 1108 private void initVelocityTracker() { 1109 if (mVelocityTracker != null) { 1110 mVelocityTracker.recycle(); 1111 } 1112 mVelocityTracker = VelocityTracker.obtain(); 1113 } 1114 1115 private float getCurrentVelocity() { 1116 if (mVelocityTracker == null) { 1117 return 0; 1118 } 1119 mVelocityTracker.computeCurrentVelocity(1000); 1120 return mVelocityTracker.getYVelocity(); 1121 } 1122 1123 private void cancelAnimation() { 1124 if (mQsExpansionAnimator != null) { 1125 mQsExpansionAnimator.cancel(); 1126 } 1127 } 1128 1129 private void flingSettings(float vel, boolean expand) { 1130 flingSettings(vel, expand, null); 1131 } 1132 1133 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) { 1134 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1135 if (target == mQsExpansionHeight) { 1136 mScrollYOverride = -1; 1137 if (onFinishRunnable != null) { 1138 onFinishRunnable.run(); 1139 } 1140 return; 1141 } 1142 boolean belowFalsingThreshold = isBelowFalsingThreshold(); 1143 if (belowFalsingThreshold) { 1144 vel = 0; 1145 } 1146 mScrollView.setBlockFlinging(true); 1147 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1148 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1149 if (belowFalsingThreshold) { 1150 animator.setDuration(350); 1151 } 1152 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1153 @Override 1154 public void onAnimationUpdate(ValueAnimator animation) { 1155 setQsExpansion((Float) animation.getAnimatedValue()); 1156 } 1157 }); 1158 animator.addListener(new AnimatorListenerAdapter() { 1159 @Override 1160 public void onAnimationEnd(Animator animation) { 1161 mScrollView.setBlockFlinging(false); 1162 mScrollYOverride = -1; 1163 mQsExpansionAnimator = null; 1164 if (onFinishRunnable != null) { 1165 onFinishRunnable.run(); 1166 } 1167 } 1168 }); 1169 animator.start(); 1170 mQsExpansionAnimator = animator; 1171 mQsAnimatorExpand = expand; 1172 } 1173 1174 /** 1175 * @return Whether we should intercept a gesture to open Quick Settings. 1176 */ 1177 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1178 if (!mQsExpansionEnabled) { 1179 return false; 1180 } 1181 View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; 1182 boolean onHeader = x >= header.getLeft() && x <= header.getRight() 1183 && y >= header.getTop() && y <= header.getBottom(); 1184 if (mQsExpanded) { 1185 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); 1186 } else { 1187 return onHeader; 1188 } 1189 } 1190 1191 @Override 1192 protected boolean isScrolledToBottom() { 1193 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1194 return true; 1195 } 1196 if (!isInSettings()) { 1197 return mNotificationStackScroller.isScrolledToBottom(); 1198 } else { 1199 return mScrollView.isScrolledToBottom(); 1200 } 1201 } 1202 1203 @Override 1204 protected int getMaxPanelHeight() { 1205 int min = mStatusBarMinHeight; 1206 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1207 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1208 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 1209 * HEADER_RUBBERBAND_FACTOR); 1210 min = Math.max(min, minHeight); 1211 } 1212 int maxHeight; 1213 if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1214 maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade()); 1215 } else { 1216 maxHeight = calculatePanelHeightShade(); 1217 } 1218 maxHeight = Math.max(maxHeight, min); 1219 return maxHeight; 1220 } 1221 1222 private boolean isInSettings() { 1223 return mQsExpanded; 1224 } 1225 1226 @Override 1227 protected void onHeightUpdated(float expandedHeight) { 1228 if (!mQsExpanded) { 1229 positionClockAndNotifications(); 1230 } 1231 if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1232 && !mQsExpansionFromOverscroll) { 1233 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1234 + mNotificationStackScroller.getMinStackHeight() 1235 + mNotificationStackScroller.getNotificationTopPadding(); 1236 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1237 float t = (expandedHeight - panelHeightQsCollapsed) 1238 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1239 1240 setQsExpansion(mQsMinExpansionHeight 1241 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1242 } 1243 mNotificationStackScroller.setStackHeight(expandedHeight); 1244 updateHeader(); 1245 updateUnlockIcon(); 1246 updateNotificationTranslucency(); 1247 } 1248 1249 /** 1250 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1251 * collapsing QS / the panel when QS was scrolled 1252 */ 1253 private int getTempQsMaxExpansion() { 1254 int qsTempMaxExpansion = mQsMaxExpansionHeight; 1255 if (mScrollYOverride != -1) { 1256 qsTempMaxExpansion -= mScrollYOverride; 1257 } 1258 return qsTempMaxExpansion; 1259 } 1260 1261 private int calculatePanelHeightShade() { 1262 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1263 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1264 - mTopPaddingAdjustment; 1265 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1266 return maxHeight; 1267 } 1268 1269 private int calculatePanelHeightQsExpanded() { 1270 float notificationHeight = mNotificationStackScroller.getHeight() 1271 - mNotificationStackScroller.getEmptyBottomMargin() 1272 - mNotificationStackScroller.getTopPadding(); 1273 float totalHeight = mQsMaxExpansionHeight + notificationHeight 1274 + mNotificationStackScroller.getNotificationTopPadding(); 1275 if (totalHeight > mNotificationStackScroller.getHeight()) { 1276 float fullyCollapsedHeight = mQsMaxExpansionHeight 1277 + mNotificationStackScroller.getMinStackHeight() 1278 + mNotificationStackScroller.getNotificationTopPadding() 1279 - getScrollViewScrollY(); 1280 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1281 } 1282 return (int) totalHeight; 1283 } 1284 1285 private int getScrollViewScrollY() { 1286 if (mScrollYOverride != -1) { 1287 return mScrollYOverride; 1288 } else { 1289 return mScrollView.getScrollY(); 1290 } 1291 } 1292 private void updateNotificationTranslucency() { 1293 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 1294 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1295 - mNotificationStackScroller.getCollapseSecondCardPadding()); 1296 alpha = Math.max(0, Math.min(alpha, 1)); 1297 alpha = (float) Math.pow(alpha, 0.75); 1298 if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { 1299 mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); 1300 } else if (alpha == 1f 1301 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { 1302 mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); 1303 } 1304 mNotificationStackScroller.setAlpha(alpha); 1305 } 1306 1307 @Override 1308 protected float getOverExpansionAmount() { 1309 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1310 } 1311 1312 @Override 1313 protected float getOverExpansionPixels() { 1314 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1315 } 1316 1317 private void updateUnlockIcon() { 1318 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1319 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1320 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1321 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1322 if (active && !mUnlockIconActive && mTracking) { 1323 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 1324 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1325 mFastOutLinearInterpolator); 1326 } else if (!active && mUnlockIconActive && mTracking) { 1327 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 1328 150, mFastOutLinearInterpolator, null); 1329 lockIcon.setImageScale(1.0f, true, 150, 1330 mFastOutLinearInterpolator); 1331 } 1332 mUnlockIconActive = active; 1333 } 1334 } 1335 1336 /** 1337 * Hides the header when notifications are colliding with it. 1338 */ 1339 private void updateHeader() { 1340 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1341 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1342 updateHeaderKeyguard(); 1343 } else { 1344 updateHeaderShade(); 1345 } 1346 1347 } 1348 1349 private void updateHeaderShade() { 1350 if (!mHeaderAnimatingIn) { 1351 mHeader.setTranslationY(getHeaderTranslation()); 1352 } 1353 setQsTranslation(mQsExpansionHeight); 1354 } 1355 1356 private float getHeaderTranslation() { 1357 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1358 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1359 return 0; 1360 } 1361 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1362 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1363 return 0; 1364 } else { 1365 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1366 } 1367 } 1368 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1369 } 1370 1371 private void updateHeaderKeyguard() { 1372 float alphaNotifications; 1373 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1374 1375 // When on Keyguard, we hide the header as soon as the top card of the notification 1376 // stack scroller is close enough (collision distance) to the bottom of the header. 1377 alphaNotifications = getNotificationsTopY() 1378 / 1379 (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); 1380 } else { 1381 1382 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1383 // soon as we start translating the stack. 1384 alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); 1385 } 1386 alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1); 1387 alphaNotifications = (float) Math.pow(alphaNotifications, 0.75); 1388 float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); 1389 mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)); 1390 mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications)); 1391 setQsTranslation(mQsExpansionHeight); 1392 } 1393 1394 private float getNotificationsTopY() { 1395 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1396 return getExpandedHeight(); 1397 } 1398 return mNotificationStackScroller.getNotificationsTopY(); 1399 } 1400 1401 @Override 1402 protected void onExpandingStarted() { 1403 super.onExpandingStarted(); 1404 mNotificationStackScroller.onExpansionStarted(); 1405 mIsExpanding = true; 1406 mQsExpandedWhenExpandingStarted = mQsExpanded; 1407 if (mQsExpanded) { 1408 onQsExpansionStarted(); 1409 } 1410 } 1411 1412 @Override 1413 protected void onExpandingFinished() { 1414 super.onExpandingFinished(); 1415 mNotificationStackScroller.onExpansionStopped(); 1416 mIsExpanding = false; 1417 mScrollYOverride = -1; 1418 if (mExpandedHeight == 0f) { 1419 setListening(false); 1420 } else { 1421 setListening(true); 1422 } 1423 mTwoFingerQsExpand = false; 1424 mTwoFingerQsExpandPossible = false; 1425 } 1426 1427 private void setListening(boolean listening) { 1428 mHeader.setListening(listening); 1429 mKeyguardStatusBar.setListening(listening); 1430 mQsPanel.setListening(listening); 1431 } 1432 1433 @Override 1434 public void instantExpand() { 1435 super.instantExpand(); 1436 setListening(true); 1437 } 1438 1439 @Override 1440 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1441 if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { 1442 return; 1443 } 1444 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1445 mNotificationStackScroller.setOnHeightChangedListener(null); 1446 if (isPixels) { 1447 mNotificationStackScroller.setOverScrolledPixels( 1448 overExpansion, true /* onTop */, false /* animate */); 1449 } else { 1450 mNotificationStackScroller.setOverScrollAmount( 1451 overExpansion, true /* onTop */, false /* animate */); 1452 } 1453 mNotificationStackScroller.setOnHeightChangedListener(this); 1454 } 1455 } 1456 1457 @Override 1458 protected void onTrackingStarted() { 1459 super.onTrackingStarted(); 1460 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1461 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1462 mAfforanceHelper.animateHideLeftRightIcon(); 1463 } else if (mQsExpanded) { 1464 mTwoFingerQsExpand = true; 1465 } 1466 } 1467 1468 @Override 1469 protected void onTrackingStopped(boolean expand) { 1470 super.onTrackingStopped(expand); 1471 if (expand) { 1472 mNotificationStackScroller.setOverScrolledPixels( 1473 0.0f, true /* onTop */, true /* animate */); 1474 } 1475 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1476 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1477 if (!mHintAnimationRunning) { 1478 mAfforanceHelper.reset(true); 1479 } 1480 } 1481 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1482 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1483 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1484 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1485 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1486 } 1487 } 1488 1489 @Override 1490 public void onHeightChanged(ExpandableView view) { 1491 1492 // Block update if we are in quick settings and just the top padding changed 1493 // (i.e. view == null). 1494 if (view == null && mQsExpanded) { 1495 return; 1496 } 1497 requestPanelHeightUpdate(); 1498 } 1499 1500 @Override 1501 public void onReset(ExpandableView view) { 1502 } 1503 1504 @Override 1505 public void onScrollChanged() { 1506 if (mQsExpanded) { 1507 requestScrollerTopPaddingUpdate(false /* animate */); 1508 requestPanelHeightUpdate(); 1509 } 1510 } 1511 1512 @Override 1513 protected void onConfigurationChanged(Configuration newConfig) { 1514 super.onConfigurationChanged(newConfig); 1515 mAfforanceHelper.onConfigurationChanged(); 1516 } 1517 1518 @Override 1519 public void onClick(View v) { 1520 if (v == mHeader) { 1521 onQsExpansionStarted(); 1522 if (mQsExpanded) { 1523 flingSettings(0 /* vel */, false /* expand */); 1524 } else if (mQsExpansionEnabled) { 1525 flingSettings(0 /* vel */, true /* expand */); 1526 } 1527 } 1528 } 1529 1530 @Override 1531 public void onAnimationToSideStarted(boolean rightPage) { 1532 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1533 mIsLaunchTransitionRunning = true; 1534 mLaunchAnimationEndRunnable = null; 1535 if (start) { 1536 mKeyguardBottomArea.launchPhone(); 1537 } else { 1538 mSecureCameraLaunchManager.startSecureCameraLaunch(); 1539 } 1540 mBlockTouches = true; 1541 } 1542 1543 @Override 1544 public void onAnimationToSideEnded() { 1545 mIsLaunchTransitionRunning = false; 1546 mIsLaunchTransitionFinished = true; 1547 if (mLaunchAnimationEndRunnable != null) { 1548 mLaunchAnimationEndRunnable.run(); 1549 mLaunchAnimationEndRunnable = null; 1550 } 1551 } 1552 1553 @Override 1554 protected void onEdgeClicked(boolean right) { 1555 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1556 || (!right && getLeftIcon().getVisibility() != View.VISIBLE) 1557 || isDozing()) { 1558 return; 1559 } 1560 mHintAnimationRunning = true; 1561 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1562 @Override 1563 public void run() { 1564 mHintAnimationRunning = false; 1565 mStatusBar.onHintFinished(); 1566 } 1567 }); 1568 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1569 if (start) { 1570 mStatusBar.onPhoneHintStarted(); 1571 } else { 1572 mStatusBar.onCameraHintStarted(); 1573 } 1574 } 1575 1576 @Override 1577 protected void startUnlockHintAnimation() { 1578 super.startUnlockHintAnimation(); 1579 startHighlightIconAnimation(getCenterIcon()); 1580 } 1581 1582 /** 1583 * Starts the highlight (making it fully opaque) animation on an icon. 1584 */ 1585 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1586 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1587 mFastOutSlowInInterpolator, new Runnable() { 1588 @Override 1589 public void run() { 1590 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1591 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1592 mFastOutSlowInInterpolator, null); 1593 } 1594 }); 1595 } 1596 1597 @Override 1598 public float getPageWidth() { 1599 return getWidth(); 1600 } 1601 1602 @Override 1603 public void onSwipingStarted() { 1604 mSecureCameraLaunchManager.onSwipingStarted(); 1605 requestDisallowInterceptTouchEvent(true); 1606 mOnlyAffordanceInThisMotion = true; 1607 } 1608 1609 @Override 1610 public KeyguardAffordanceView getLeftIcon() { 1611 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1612 ? mKeyguardBottomArea.getCameraView() 1613 : mKeyguardBottomArea.getPhoneView(); 1614 } 1615 1616 @Override 1617 public KeyguardAffordanceView getCenterIcon() { 1618 return mKeyguardBottomArea.getLockIcon(); 1619 } 1620 1621 @Override 1622 public KeyguardAffordanceView getRightIcon() { 1623 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1624 ? mKeyguardBottomArea.getPhoneView() 1625 : mKeyguardBottomArea.getCameraView(); 1626 } 1627 1628 @Override 1629 public View getLeftPreview() { 1630 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1631 ? mKeyguardBottomArea.getCameraPreview() 1632 : mKeyguardBottomArea.getPhonePreview(); 1633 } 1634 1635 @Override 1636 public View getRightPreview() { 1637 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1638 ? mKeyguardBottomArea.getPhonePreview() 1639 : mKeyguardBottomArea.getCameraPreview(); 1640 } 1641 1642 @Override 1643 public float getAffordanceFalsingFactor() { 1644 return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 1645 } 1646 1647 @Override 1648 protected float getPeekHeight() { 1649 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1650 return mNotificationStackScroller.getPeekHeight(); 1651 } else { 1652 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1653 } 1654 } 1655 1656 @Override 1657 protected float getCannedFlingDurationFactor() { 1658 if (mQsExpanded) { 1659 return 0.7f; 1660 } else { 1661 return 0.6f; 1662 } 1663 } 1664 1665 @Override 1666 protected boolean fullyExpandedClearAllVisible() { 1667 return mNotificationStackScroller.isDismissViewNotGone() 1668 && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand; 1669 } 1670 1671 @Override 1672 protected boolean isClearAllVisible() { 1673 return mNotificationStackScroller.isDismissViewVisible(); 1674 } 1675 1676 @Override 1677 protected int getClearAllHeight() { 1678 return mNotificationStackScroller.getDismissViewHeight(); 1679 } 1680 1681 @Override 1682 protected boolean isTrackingBlocked() { 1683 return mConflictingQsExpansionGesture && mQsExpanded; 1684 } 1685 1686 public void notifyVisibleChildrenChanged() { 1687 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1688 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1689 } else { 1690 mReserveNotificationSpace.setVisibility(View.GONE); 1691 } 1692 } 1693 1694 public boolean isQsExpanded() { 1695 return mQsExpanded; 1696 } 1697 1698 public boolean isQsDetailShowing() { 1699 return mQsPanel.isShowingDetail(); 1700 } 1701 1702 public void closeQsDetail() { 1703 mQsPanel.closeDetail(); 1704 } 1705 1706 @Override 1707 public boolean shouldDelayChildPressedState() { 1708 return true; 1709 } 1710 1711 public boolean isLaunchTransitionFinished() { 1712 return mIsLaunchTransitionFinished; 1713 } 1714 1715 public boolean isLaunchTransitionRunning() { 1716 return mIsLaunchTransitionRunning; 1717 } 1718 1719 public void setLaunchTransitionEndRunnable(Runnable r) { 1720 mLaunchAnimationEndRunnable = r; 1721 } 1722 1723 public void setEmptyDragAmount(float amount) { 1724 float factor = 0.8f; 1725 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1726 factor = 0.4f; 1727 } else if (!mStatusBar.hasActiveNotifications()) { 1728 factor = 0.4f; 1729 } 1730 mEmptyDragAmount = amount * factor; 1731 positionClockAndNotifications(); 1732 } 1733 1734 private static float interpolate(float t, float start, float end) { 1735 return (1 - t) * start + t * end; 1736 } 1737 1738 private void updateKeyguardStatusBarVisibility() { 1739 mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mDozing ? VISIBLE : INVISIBLE); 1740 } 1741 1742 public void setDozing(boolean dozing) { 1743 if (dozing == mDozing) return; 1744 mDozing = dozing; 1745 if (mDozing) { 1746 setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0xff, false /*animate*/); 1747 } else { 1748 setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0, true /*animate*/); 1749 } 1750 updateKeyguardStatusBarVisibility(); 1751 } 1752 1753 @Override 1754 public boolean isDozing() { 1755 return mDozing; 1756 } 1757 1758 private static void setBackgroundColorAlpha(final View target, int rgb, int targetAlpha, 1759 boolean animate) { 1760 int currentAlpha = getBackgroundAlpha(target); 1761 if (currentAlpha == targetAlpha) { 1762 return; 1763 } 1764 final int r = Color.red(rgb); 1765 final int g = Color.green(rgb); 1766 final int b = Color.blue(rgb); 1767 Object runningAnim = target.getTag(TAG_KEY_ANIM); 1768 if (runningAnim instanceof ValueAnimator) { 1769 ((ValueAnimator) runningAnim).cancel(); 1770 } 1771 if (!animate) { 1772 target.setBackgroundColor(Color.argb(targetAlpha, r, g, b)); 1773 return; 1774 } 1775 ValueAnimator anim = ValueAnimator.ofInt(currentAlpha, targetAlpha); 1776 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1777 @Override 1778 public void onAnimationUpdate(ValueAnimator animation) { 1779 int value = (int) animation.getAnimatedValue(); 1780 target.setBackgroundColor(Color.argb(value, r, g, b)); 1781 } 1782 }); 1783 anim.setDuration(DOZE_BACKGROUND_ANIM_DURATION); 1784 anim.addListener(new AnimatorListenerAdapter() { 1785 @Override 1786 public void onAnimationEnd(Animator animation) { 1787 target.setTag(TAG_KEY_ANIM, null); 1788 } 1789 }); 1790 anim.start(); 1791 target.setTag(TAG_KEY_ANIM, anim); 1792 } 1793 1794 private static int getBackgroundAlpha(View view) { 1795 if (view.getBackground() instanceof ColorDrawable) { 1796 ColorDrawable drawable = (ColorDrawable) view.getBackground(); 1797 return Color.alpha(drawable.getColor()); 1798 } else { 1799 return 0; 1800 } 1801 } 1802 1803 public void setShadeEmpty(boolean shadeEmpty) { 1804 mShadeEmpty = shadeEmpty; 1805 updateEmptyShadeView(); 1806 } 1807 1808 private void updateEmptyShadeView() { 1809 1810 // Hide "No notifications" in QS. 1811 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 1812 } 1813 1814 public void setQsScrimEnabled(boolean qsScrimEnabled) { 1815 boolean changed = mQsScrimEnabled != qsScrimEnabled; 1816 mQsScrimEnabled = qsScrimEnabled; 1817 if (changed) { 1818 updateQsState(); 1819 } 1820 } 1821 1822 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 1823 mKeyguardUserSwitcher = keyguardUserSwitcher; 1824 } 1825 1826 private final Runnable mUpdateHeader = new Runnable() { 1827 @Override 1828 public void run() { 1829 mHeader.updateEverything(); 1830 } 1831 }; 1832 1833 public void onScreenTurnedOn() { 1834 mKeyguardStatusView.refreshTime(); 1835 } 1836 } 1837