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