NotificationPanelView.java revision b3f0a2ff000ca2481f95ee8f7d7b487e33b87eca
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.ValueAnimator; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.view.MotionEvent; 28import android.view.VelocityTracker; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.ViewTreeObserver; 32import android.view.accessibility.AccessibilityEvent; 33import android.view.animation.AnimationUtils; 34import android.view.animation.Interpolator; 35import android.widget.LinearLayout; 36 37import com.android.systemui.R; 38import com.android.systemui.statusbar.ExpandableView; 39import com.android.systemui.statusbar.FlingAnimationUtils; 40import com.android.systemui.statusbar.GestureRecorder; 41import com.android.systemui.statusbar.StatusBarState; 42import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 43import com.android.systemui.statusbar.stack.StackStateAnimator; 44 45import java.util.ArrayList; 46 47public class NotificationPanelView extends PanelView implements 48 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 49 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 50 KeyguardPageSwipeHelper.Callback { 51 52 private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f; 53 54 private KeyguardPageSwipeHelper mPageSwiper; 55 private StatusBarHeaderView mHeader; 56 private View mQsContainer; 57 private View mQsPanel; 58 private View mKeyguardStatusView; 59 private ObservableScrollView mScrollView; 60 private View mStackScrollerContainer; 61 62 private NotificationStackScrollLayout mNotificationStackScroller; 63 private int mNotificationTopPadding; 64 private boolean mAnimateNextTopPaddingChange; 65 66 private int mTrackingPointer; 67 private VelocityTracker mVelocityTracker; 68 private boolean mQsTracking; 69 70 /** 71 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 72 * intercepted yet. 73 */ 74 private boolean mIntercepting; 75 private boolean mQsExpanded; 76 private boolean mQsFullyExpanded; 77 private boolean mKeyguardShowing; 78 private float mInitialHeightOnTouch; 79 private float mInitialTouchX; 80 private float mInitialTouchY; 81 private float mLastTouchX; 82 private float mLastTouchY; 83 private float mQsExpansionHeight; 84 private int mQsMinExpansionHeight; 85 private int mQsMaxExpansionHeight; 86 private int mMinStackHeight; 87 private int mQsPeekHeight; 88 private float mNotificationTranslation; 89 private int mStackScrollerIntrinsicPadding; 90 private boolean mStackScrollerOverscrolling; 91 private boolean mQsExpansionEnabled = true; 92 private ValueAnimator mQsExpansionAnimator; 93 private FlingAnimationUtils mFlingAnimationUtils; 94 private int mStatusBarMinHeight; 95 private boolean mHeaderHidden; 96 private int mNotificationsHeaderCollideDistance; 97 98 private Interpolator mFastOutSlowInInterpolator; 99 private Interpolator mFastOutLinearInterpolator; 100 private Interpolator mLinearOutSlowInInterpolator; 101 private ObjectAnimator mClockAnimator; 102 private int mClockAnimationTarget = -1; 103 private int mTopPaddingAdjustment; 104 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 105 new KeyguardClockPositionAlgorithm(); 106 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 107 new KeyguardClockPositionAlgorithm.Result(); 108 private boolean mIsSwipedHorizontally; 109 private boolean mIsExpanding; 110 private KeyguardBottomAreaView mKeyguardBottomArea; 111 private boolean mBlockTouches; 112 private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); 113 114 public NotificationPanelView(Context context, AttributeSet attrs) { 115 super(context, attrs); 116 } 117 118 public void setStatusBar(PhoneStatusBar bar) { 119 if (mStatusBar != null) { 120 mStatusBar.setOnFlipRunnable(null); 121 } 122 mStatusBar = bar; 123 if (bar != null) { 124 mStatusBar.setOnFlipRunnable(new Runnable() { 125 @Override 126 public void run() { 127 requestPanelHeightUpdate(); 128 } 129 }); 130 } 131 } 132 133 @Override 134 protected void onFinishInflate() { 135 super.onFinishInflate(); 136 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 137 mHeader.getBackgroundView().setOnClickListener(this); 138 mHeader.setOverlayParent(this); 139 mKeyguardStatusView = findViewById(R.id.keyguard_status_view); 140 mStackScrollerContainer = findViewById(R.id.notification_container_parent); 141 mQsContainer = findViewById(R.id.quick_settings_container); 142 mQsPanel = findViewById(R.id.quick_settings_panel); 143 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 144 mScrollView.setListener(this); 145 mNotificationStackScroller = (NotificationStackScrollLayout) 146 findViewById(R.id.notification_stack_scroller); 147 mNotificationStackScroller.setOnHeightChangedListener(this); 148 mNotificationStackScroller.setOverscrollTopChangedListener(this); 149 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 150 android.R.interpolator.fast_out_slow_in); 151 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 152 android.R.interpolator.linear_out_slow_in); 153 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 154 android.R.interpolator.fast_out_linear_in); 155 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 156 mSwipeTranslationViews.add(mNotificationStackScroller); 157 mSwipeTranslationViews.add(mKeyguardStatusView); 158 mPageSwiper = new KeyguardPageSwipeHelper(this, getContext()); 159 } 160 161 @Override 162 protected void loadDimens() { 163 super.loadDimens(); 164 mNotificationTopPadding = getResources().getDimensionPixelSize( 165 R.dimen.notifications_top_padding); 166 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height); 167 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 168 mStatusBarMinHeight = getResources().getDimensionPixelSize( 169 com.android.internal.R.dimen.status_bar_height); 170 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 171 mNotificationsHeaderCollideDistance = 172 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 173 mClockPositionAlgorithm.loadDimens(getResources()); 174 } 175 176 @Override 177 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 178 super.onLayout(changed, left, top, right, bottom); 179 180 // Calculate quick setting heights. 181 mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight; 182 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 183 if (mQsExpanded) { 184 if (mQsFullyExpanded) { 185 setQsStackScrollerPadding(mQsMaxExpansionHeight); 186 } 187 } else { 188 if (!mStackScrollerOverscrolling) { 189 setQsExpansion(mQsMinExpansionHeight); 190 } 191 positionClockAndNotifications(); 192 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 193 } 194 } 195 196 /** 197 * Positions the clock and notifications dynamically depending on how many notifications are 198 * showing. 199 */ 200 private void positionClockAndNotifications() { 201 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 202 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 203 int bottom = mStackScrollerOverscrolling 204 ? mHeader.getCollapsedHeight() 205 : mHeader.getBottom(); 206 mStackScrollerIntrinsicPadding = bottom + mQsPeekHeight 207 + mNotificationTopPadding; 208 mTopPaddingAdjustment = 0; 209 } else { 210 mClockPositionAlgorithm.setup( 211 mStatusBar.getMaxKeyguardNotifications(), 212 getMaxPanelHeight(), 213 getExpandedHeight(), 214 mNotificationStackScroller.getNotGoneChildCount(), 215 getHeight(), 216 mKeyguardStatusView.getHeight()); 217 mClockPositionAlgorithm.run(mClockPositionResult); 218 if (animateClock || mClockAnimator != null) { 219 startClockAnimation(mClockPositionResult.clockY); 220 } else { 221 mKeyguardStatusView.setY(mClockPositionResult.clockY); 222 } 223 applyClockAlpha(mClockPositionResult.clockAlpha); 224 mStackScrollerIntrinsicPadding = mClockPositionResult.stackScrollerPadding; 225 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 226 } 227 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding, 228 mAnimateNextTopPaddingChange || animateClock); 229 mAnimateNextTopPaddingChange = false; 230 } 231 232 private void startClockAnimation(int y) { 233 if (mClockAnimationTarget == y) { 234 return; 235 } 236 mClockAnimationTarget = y; 237 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 238 @Override 239 public boolean onPreDraw() { 240 getViewTreeObserver().removeOnPreDrawListener(this); 241 if (mClockAnimator != null) { 242 mClockAnimator.removeAllListeners(); 243 mClockAnimator.cancel(); 244 } 245 mClockAnimator = 246 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 247 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 248 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 249 mClockAnimator.addListener(new AnimatorListenerAdapter() { 250 @Override 251 public void onAnimationEnd(Animator animation) { 252 mClockAnimator = null; 253 mClockAnimationTarget = -1; 254 } 255 }); 256 mClockAnimator.start(); 257 return true; 258 } 259 }); 260 } 261 262 private void applyClockAlpha(float alpha) { 263 if (alpha != 1.0f) { 264 mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null); 265 } else { 266 mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null); 267 } 268 mKeyguardStatusView.setAlpha(alpha); 269 } 270 271 public void animateToFullShade() { 272 mAnimateNextTopPaddingChange = true; 273 mNotificationStackScroller.goToFullShade(); 274 requestLayout(); 275 } 276 277 /** 278 * @return Whether Quick Settings are currently expanded. 279 */ 280 public boolean isQsExpanded() { 281 return mQsExpanded; 282 } 283 284 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 285 mQsExpansionEnabled = qsExpansionEnabled; 286 } 287 288 @Override 289 public void resetViews() { 290 mBlockTouches = false; 291 mPageSwiper.reset(); 292 closeQs(); 293 } 294 295 public void closeQs() { 296 cancelAnimation(); 297 setQsExpansion(mQsMinExpansionHeight); 298 } 299 300 public void openQs() { 301 cancelAnimation(); 302 if (mQsExpansionEnabled) { 303 setQsExpansion(mQsMaxExpansionHeight); 304 } 305 } 306 307 @Override 308 public void fling(float vel, boolean always) { 309 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 310 if (gr != null) { 311 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 312 } 313 super.fling(vel, always); 314 } 315 316 @Override 317 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 318 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 319 event.getText() 320 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 321 return true; 322 } 323 324 return super.dispatchPopulateAccessibilityEvent(event); 325 } 326 327 @Override 328 public boolean onInterceptTouchEvent(MotionEvent event) { 329 if (mBlockTouches) { 330 return false; 331 } 332 int pointerIndex = event.findPointerIndex(mTrackingPointer); 333 if (pointerIndex < 0) { 334 pointerIndex = 0; 335 mTrackingPointer = event.getPointerId(pointerIndex); 336 } 337 final float x = event.getX(pointerIndex); 338 final float y = event.getY(pointerIndex); 339 340 switch (event.getActionMasked()) { 341 case MotionEvent.ACTION_DOWN: 342 mIntercepting = true; 343 mInitialTouchY = y; 344 mInitialTouchX = x; 345 initVelocityTracker(); 346 trackMovement(event); 347 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 348 getParent().requestDisallowInterceptTouchEvent(true); 349 } 350 break; 351 case MotionEvent.ACTION_POINTER_UP: 352 final int upPointer = event.getPointerId(event.getActionIndex()); 353 if (mTrackingPointer == upPointer) { 354 // gesture is ongoing, find a new pointer to track 355 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 356 mTrackingPointer = event.getPointerId(newIndex); 357 mInitialTouchX = event.getX(newIndex); 358 mInitialTouchY = event.getY(newIndex); 359 } 360 break; 361 362 case MotionEvent.ACTION_MOVE: 363 final float h = y - mInitialTouchY; 364 trackMovement(event); 365 if (mQsTracking) { 366 367 // Already tracking because onOverscrolled was called. We need to update here 368 // so we don't stop for a frame until the next touch event gets handled in 369 // onTouchEvent. 370 setQsExpansion(h + mInitialHeightOnTouch); 371 trackMovement(event); 372 mIntercepting = false; 373 return true; 374 } 375 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 376 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 377 onQsExpansionStarted(); 378 mInitialHeightOnTouch = mQsExpansionHeight; 379 mInitialTouchY = y; 380 mInitialTouchX = x; 381 mQsTracking = true; 382 mIntercepting = false; 383 return true; 384 } 385 break; 386 387 case MotionEvent.ACTION_CANCEL: 388 case MotionEvent.ACTION_UP: 389 trackMovement(event); 390 if (mQsTracking) { 391 flingQsWithCurrentVelocity(); 392 mQsTracking = false; 393 } 394 mIntercepting = false; 395 break; 396 } 397 return !mQsExpanded && super.onInterceptTouchEvent(event); 398 } 399 400 @Override 401 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 402 403 // Block request when interacting with the scroll view so we can still intercept the 404 // scrolling when QS is expanded. 405 if (mScrollView.isDispatchingTouchEvent()) { 406 return; 407 } 408 super.requestDisallowInterceptTouchEvent(disallowIntercept); 409 } 410 411 private void flingQsWithCurrentVelocity() { 412 float vel = getCurrentVelocity(); 413 414 // TODO: Better logic whether we should expand or not. 415 flingSettings(vel, vel > 0); 416 } 417 418 @Override 419 public boolean onTouchEvent(MotionEvent event) { 420 if (mBlockTouches) { 421 return false; 422 } 423 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference 424 // implementation. 425 if ((!mIsExpanding || mHintAnimationRunning) 426 && !mQsExpanded 427 && mStatusBar.getBarState() != StatusBarState.SHADE) { 428 mPageSwiper.onTouchEvent(event); 429 if (mPageSwiper.isSwipingInProgress()) { 430 return true; 431 } 432 } 433 if (mQsTracking || mQsExpanded) { 434 return onQsTouch(event); 435 } 436 437 super.onTouchEvent(event); 438 return true; 439 } 440 441 @Override 442 protected boolean hasConflictingGestures() { 443 return mStatusBar.getBarState() != StatusBarState.SHADE; 444 } 445 446 private boolean onQsTouch(MotionEvent event) { 447 int pointerIndex = event.findPointerIndex(mTrackingPointer); 448 if (pointerIndex < 0) { 449 pointerIndex = 0; 450 mTrackingPointer = event.getPointerId(pointerIndex); 451 } 452 final float y = event.getY(pointerIndex); 453 final float x = event.getX(pointerIndex); 454 455 switch (event.getActionMasked()) { 456 case MotionEvent.ACTION_DOWN: 457 mQsTracking = true; 458 mInitialTouchY = y; 459 mInitialTouchX = x; 460 onQsExpansionStarted(); 461 mInitialHeightOnTouch = mQsExpansionHeight; 462 initVelocityTracker(); 463 trackMovement(event); 464 break; 465 466 case MotionEvent.ACTION_POINTER_UP: 467 final int upPointer = event.getPointerId(event.getActionIndex()); 468 if (mTrackingPointer == upPointer) { 469 // gesture is ongoing, find a new pointer to track 470 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 471 final float newY = event.getY(newIndex); 472 final float newX = event.getX(newIndex); 473 mTrackingPointer = event.getPointerId(newIndex); 474 mInitialHeightOnTouch = mQsExpansionHeight; 475 mInitialTouchY = newY; 476 mInitialTouchX = newX; 477 } 478 break; 479 480 case MotionEvent.ACTION_MOVE: 481 final float h = y - mInitialTouchY; 482 setQsExpansion(h + mInitialHeightOnTouch); 483 trackMovement(event); 484 break; 485 486 case MotionEvent.ACTION_UP: 487 case MotionEvent.ACTION_CANCEL: 488 mQsTracking = false; 489 mTrackingPointer = -1; 490 trackMovement(event); 491 flingQsWithCurrentVelocity(); 492 if (mVelocityTracker != null) { 493 mVelocityTracker.recycle(); 494 mVelocityTracker = null; 495 } 496 break; 497 } 498 return true; 499 } 500 501 @Override 502 public void onOverscrolled(int amount) { 503 if (mIntercepting) { 504 onQsExpansionStarted(amount); 505 mInitialHeightOnTouch = mQsExpansionHeight; 506 mInitialTouchY = mLastTouchY; 507 mInitialTouchX = mLastTouchX; 508 mQsTracking = true; 509 } 510 } 511 512 513 @Override 514 public void onOverscrollTopChanged(float amount) { 515 cancelAnimation(); 516 float rounded = amount >= 1f ? amount : 0f; 517 mStackScrollerOverscrolling = rounded != 0f; 518 setQsExpansion(mQsMinExpansionHeight + rounded); 519 updateQsState(); 520 } 521 522 private void onQsExpansionStarted() { 523 onQsExpansionStarted(0); 524 } 525 526 private void onQsExpansionStarted(int overscrollAmount) { 527 cancelAnimation(); 528 529 // Reset scroll position and apply that position to the expanded height. 530 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 531 mScrollView.scrollTo(0, 0); 532 setQsExpansion(height); 533 } 534 535 private void setQsExpanded(boolean expanded) { 536 boolean changed = mQsExpanded != expanded; 537 if (changed) { 538 mQsExpanded = expanded; 539 updateQsState(); 540 } 541 } 542 543 public void setKeyguardShowing(boolean keyguardShowing) { 544 mKeyguardShowing = keyguardShowing; 545 updateQsState(); 546 } 547 548 private void updateQsState() { 549 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 550 mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling); 551 mNotificationStackScroller.setEnabled(!mQsExpanded); 552 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 553 mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded 554 ? View.INVISIBLE 555 : View.VISIBLE); 556 mScrollView.setTouchEnabled(mQsExpanded); 557 } 558 559 private void setQsExpansion(float height) { 560 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 561 mQsFullyExpanded = height == mQsMaxExpansionHeight; 562 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 563 setQsExpanded(true); 564 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 565 setQsExpanded(false); 566 } 567 mQsExpansionHeight = height; 568 mHeader.setExpansion(height - mQsPeekHeight); 569 setQsTranslation(height); 570 if (!mStackScrollerOverscrolling) { 571 setQsStackScrollerPadding(height); 572 } 573 mStatusBar.userActivity(); 574 } 575 576 private void setQsTranslation(float height) { 577 mQsContainer.setY(height - mQsContainer.getHeight()); 578 } 579 580 private void setQsStackScrollerPadding(float height) { 581 float start = height - mScrollView.getScrollY() + mNotificationTopPadding; 582 float stackHeight = mNotificationStackScroller.getHeight() - start; 583 if (stackHeight <= mMinStackHeight) { 584 float overflow = mMinStackHeight - stackHeight; 585 stackHeight = mMinStackHeight; 586 start = mNotificationStackScroller.getHeight() - stackHeight; 587 mNotificationStackScroller.setTranslationY(overflow); 588 mNotificationTranslation = overflow + mScrollView.getScrollY(); 589 } else { 590 mNotificationStackScroller.setTranslationY(0); 591 mNotificationTranslation = mScrollView.getScrollY(); 592 } 593 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false); 594 } 595 596 private int clampQsStackScrollerPadding(int desiredPadding) { 597 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding); 598 } 599 600 private void trackMovement(MotionEvent event) { 601 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 602 mLastTouchX = event.getX(); 603 mLastTouchY = event.getY(); 604 } 605 606 private void initVelocityTracker() { 607 if (mVelocityTracker != null) { 608 mVelocityTracker.recycle(); 609 } 610 mVelocityTracker = VelocityTracker.obtain(); 611 } 612 613 private float getCurrentVelocity() { 614 if (mVelocityTracker == null) { 615 return 0; 616 } 617 mVelocityTracker.computeCurrentVelocity(1000); 618 return mVelocityTracker.getYVelocity(); 619 } 620 621 private void cancelAnimation() { 622 if (mQsExpansionAnimator != null) { 623 mQsExpansionAnimator.cancel(); 624 } 625 } 626 private void flingSettings(float vel, boolean expand) { 627 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 628 if (target == mQsExpansionHeight) { 629 return; 630 } 631 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 632 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 633 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 634 @Override 635 public void onAnimationUpdate(ValueAnimator animation) { 636 setQsExpansion((Float) animation.getAnimatedValue()); 637 } 638 }); 639 animator.addListener(new AnimatorListenerAdapter() { 640 @Override 641 public void onAnimationEnd(Animator animation) { 642 mQsExpansionAnimator = null; 643 } 644 }); 645 animator.start(); 646 mQsExpansionAnimator = animator; 647 } 648 649 /** 650 * @return Whether we should intercept a gesture to open Quick Settings. 651 */ 652 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 653 if (!mQsExpansionEnabled) { 654 return false; 655 } 656 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight() 657 && y >= mHeader.getTop() && y <= mHeader.getBottom(); 658 if (mQsExpanded) { 659 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); 660 } else { 661 return onHeader; 662 } 663 } 664 665 @Override 666 public void setVisibility(int visibility) { 667 int oldVisibility = getVisibility(); 668 super.setVisibility(visibility); 669 if (visibility != oldVisibility) { 670 reparentStatusIcons(visibility == VISIBLE); 671 } 672 } 673 674 /** 675 * When the notification panel gets expanded, we need to move the status icons in the header 676 * card. 677 */ 678 private void reparentStatusIcons(boolean toHeader) { 679 if (mStatusBar == null) { 680 return; 681 } 682 LinearLayout systemIcons = mStatusBar.getSystemIcons(); 683 if (systemIcons.getParent() != null) { 684 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons); 685 } 686 if (toHeader) { 687 mHeader.attachSystemIcons(systemIcons); 688 } else { 689 mHeader.onSystemIconsDetached(); 690 mStatusBar.reattachSystemIcons(); 691 } 692 } 693 694 @Override 695 protected boolean isScrolledToBottom() { 696 if (!isInSettings()) { 697 return mNotificationStackScroller.isScrolledToBottom(); 698 } 699 return super.isScrolledToBottom(); 700 } 701 702 @Override 703 protected int getMaxPanelHeight() { 704 // TODO: Figure out transition for collapsing when QS is open, adjust height here. 705 int maxPanelHeight = super.getMaxPanelHeight(); 706 int emptyBottomMargin = mStackScrollerContainer.getHeight() 707 - mNotificationStackScroller.getHeight() 708 + mNotificationStackScroller.getEmptyBottomMargin(); 709 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment; 710 maxHeight = Math.max(maxHeight, mStatusBarMinHeight); 711 return maxHeight; 712 } 713 714 private boolean isInSettings() { 715 return mQsExpanded; 716 } 717 718 @Override 719 protected void onHeightUpdated(float expandedHeight) { 720 if (!mQsExpanded) { 721 positionClockAndNotifications(); 722 } 723 mNotificationStackScroller.setStackHeight(expandedHeight); 724 updateKeyguardHeaderVisibility(); 725 } 726 727 /** 728 * Hides the header when notifications are colliding with it. 729 */ 730 private void updateKeyguardHeaderVisibility() { 731 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 732 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 733 boolean hidden = mNotificationStackScroller.getNotificationsTopY() 734 <= mHeader.getBottom() + mNotificationsHeaderCollideDistance; 735 if (hidden && !mHeaderHidden) { 736 mHeader.animate() 737 .alpha(0f) 738 .withLayer() 739 .translationY(-mHeader.getHeight()/2) 740 .setInterpolator(mFastOutLinearInterpolator) 741 .setDuration(200); 742 } else if (!hidden && mHeaderHidden) { 743 mHeader.animate() 744 .alpha(1f) 745 .withLayer() 746 .translationY(0) 747 .setInterpolator(mLinearOutSlowInInterpolator) 748 .setDuration(200); 749 } 750 mHeaderHidden = hidden; 751 } else { 752 mHeader.animate().cancel(); 753 mHeader.setAlpha(1f); 754 mHeader.setTranslationY(0f); 755 if (mHeader.getLayerType() != LAYER_TYPE_NONE) { 756 mHeader.setLayerType(LAYER_TYPE_NONE, null); 757 } 758 mHeaderHidden = false; 759 } 760 761 } 762 763 @Override 764 protected void onExpandingStarted() { 765 super.onExpandingStarted(); 766 mNotificationStackScroller.onExpansionStarted(); 767 mIsExpanding = true; 768 } 769 770 @Override 771 protected void onExpandingFinished() { 772 super.onExpandingFinished(); 773 mNotificationStackScroller.onExpansionStopped(); 774 mIsExpanding = false; 775 } 776 777 @Override 778 protected void onOverExpansionChanged(float overExpansion) { 779 float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true); 780 float expansionChange = overExpansion - mOverExpansion; 781 expansionChange *= EXPANSION_RUBBER_BAND_EXTRA_FACTOR; 782 mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + expansionChange, 783 true /* onTop */, 784 false /* animate */); 785 super.onOverExpansionChanged(overExpansion); 786 } 787 788 @Override 789 protected void onTrackingStarted() { 790 super.onTrackingStarted(); 791 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 792 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 793 mPageSwiper.animateHideLeftRightIcon(); 794 } 795 } 796 797 @Override 798 protected void onTrackingStopped(boolean expand) { 799 super.onTrackingStopped(expand); 800 mOverExpansion = 0.0f; 801 mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, true /* animate */); 802 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 803 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 804 mPageSwiper.showAllIcons(true); 805 } 806 } 807 808 @Override 809 public void onHeightChanged(ExpandableView view) { 810 requestPanelHeightUpdate(); 811 } 812 813 @Override 814 public void onScrollChanged() { 815 if (mQsExpanded) { 816 mNotificationStackScroller.setTranslationY( 817 mNotificationTranslation - mScrollView.getScrollY()); 818 } 819 } 820 821 @Override 822 protected void onConfigurationChanged(Configuration newConfig) { 823 super.onConfigurationChanged(newConfig); 824 mPageSwiper.onConfigurationChanged(); 825 } 826 827 @Override 828 public void onClick(View v) { 829 if (v == mHeader.getBackgroundView()) { 830 onQsExpansionStarted(); 831 if (mQsExpanded) { 832 flingSettings(0 /* vel */, false /* expand */); 833 } else if (mQsExpansionEnabled) { 834 flingSettings(0 /* vel */, true /* expand */); 835 } 836 } 837 } 838 839 @Override 840 public void onAnimationToSideStarted(boolean rightPage) { 841 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 842 if (start) { 843 mKeyguardBottomArea.launchPhone(); 844 } else { 845 mKeyguardBottomArea.launchCamera(); 846 } 847 mBlockTouches = true; 848 } 849 850 @Override 851 protected void onEdgeClicked(boolean right) { 852 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 853 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) { 854 return; 855 } 856 mHintAnimationRunning = true; 857 mPageSwiper.startHintAnimation(right, new Runnable() { 858 @Override 859 public void run() { 860 mHintAnimationRunning = false; 861 mStatusBar.onHintFinished(); 862 } 863 }); 864 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 865 if (start) { 866 mStatusBar.onPhoneHintStarted(); 867 } else { 868 mStatusBar.onCameraHintStarted(); 869 } 870 } 871 872 @Override 873 public float getPageWidth() { 874 return getWidth(); 875 } 876 877 @Override 878 public ArrayList<View> getTranslationViews() { 879 return mSwipeTranslationViews; 880 } 881 882 @Override 883 public View getLeftIcon() { 884 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 885 ? mKeyguardBottomArea.getCameraImageView() 886 : mKeyguardBottomArea.getPhoneImageView(); 887 } 888 889 @Override 890 public View getCenterIcon() { 891 return mKeyguardBottomArea.getLockIcon(); 892 } 893 894 @Override 895 public View getRightIcon() { 896 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 897 ? mKeyguardBottomArea.getPhoneImageView() 898 : mKeyguardBottomArea.getCameraImageView(); 899 } 900} 901