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