NotificationPanelView.java revision 4c6969a512cd70831249ec1d07691f16fe5465f5
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 public void resetViews() { 264 mBlockTouches = false; 265 mPageSwiper.reset(); 266 closeQs(); 267 } 268 269 public void closeQs() { 270 cancelAnimation(); 271 setQsExpansion(mQsMinExpansionHeight); 272 } 273 274 public void openQs() { 275 cancelAnimation(); 276 if (mQsExpansionEnabled) { 277 setQsExpansion(mQsMaxExpansionHeight); 278 } 279 } 280 281 @Override 282 public void fling(float vel, boolean always) { 283 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 284 if (gr != null) { 285 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 286 } 287 super.fling(vel, always); 288 } 289 290 @Override 291 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 292 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 293 event.getText() 294 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 295 return true; 296 } 297 298 return super.dispatchPopulateAccessibilityEvent(event); 299 } 300 301 @Override 302 public boolean onInterceptTouchEvent(MotionEvent event) { 303 if (mBlockTouches) { 304 return false; 305 } 306 int pointerIndex = event.findPointerIndex(mTrackingPointer); 307 if (pointerIndex < 0) { 308 pointerIndex = 0; 309 mTrackingPointer = event.getPointerId(pointerIndex); 310 } 311 final float x = event.getX(pointerIndex); 312 final float y = event.getY(pointerIndex); 313 314 switch (event.getActionMasked()) { 315 case MotionEvent.ACTION_DOWN: 316 mIntercepting = true; 317 mInitialTouchY = y; 318 mInitialTouchX = x; 319 initVelocityTracker(); 320 trackMovement(event); 321 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 322 getParent().requestDisallowInterceptTouchEvent(true); 323 } 324 break; 325 case MotionEvent.ACTION_POINTER_UP: 326 final int upPointer = event.getPointerId(event.getActionIndex()); 327 if (mTrackingPointer == upPointer) { 328 // gesture is ongoing, find a new pointer to track 329 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 330 mTrackingPointer = event.getPointerId(newIndex); 331 mInitialTouchX = event.getX(newIndex); 332 mInitialTouchY = event.getY(newIndex); 333 } 334 break; 335 336 case MotionEvent.ACTION_MOVE: 337 final float h = y - mInitialTouchY; 338 trackMovement(event); 339 if (mQsTracking) { 340 341 // Already tracking because onOverscrolled was called. We need to update here 342 // so we don't stop for a frame until the next touch event gets handled in 343 // onTouchEvent. 344 setQsExpansion(h + mInitialHeightOnTouch); 345 trackMovement(event); 346 mIntercepting = false; 347 return true; 348 } 349 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 350 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 351 onQsExpansionStarted(); 352 mInitialHeightOnTouch = mQsExpansionHeight; 353 mInitialTouchY = y; 354 mInitialTouchX = x; 355 mQsTracking = true; 356 mIntercepting = false; 357 return true; 358 } 359 break; 360 361 case MotionEvent.ACTION_CANCEL: 362 case MotionEvent.ACTION_UP: 363 trackMovement(event); 364 if (mQsTracking) { 365 flingQsWithCurrentVelocity(); 366 mQsTracking = false; 367 } 368 mIntercepting = false; 369 break; 370 } 371 return !mQsExpanded && super.onInterceptTouchEvent(event); 372 } 373 374 @Override 375 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 376 377 // Block request when interacting with the scroll view so we can still intercept the 378 // scrolling when QS is expanded. 379 if (mScrollView.isDispatchingTouchEvent()) { 380 return; 381 } 382 super.requestDisallowInterceptTouchEvent(disallowIntercept); 383 } 384 385 private void flingQsWithCurrentVelocity() { 386 float vel = getCurrentVelocity(); 387 388 // TODO: Better logic whether we should expand or not. 389 flingSettings(vel, vel > 0); 390 } 391 392 @Override 393 public boolean onTouchEvent(MotionEvent event) { 394 if (mBlockTouches) { 395 return false; 396 } 397 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference 398 // implementation. 399 if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { 400 mPageSwiper.onTouchEvent(event); 401 if (mPageSwiper.isSwipingInProgress()) { 402 return true; 403 } 404 } 405 if (mQsTracking || mQsExpanded) { 406 return onQsTouch(event); 407 } 408 409 super.onTouchEvent(event); 410 return true; 411 } 412 413 @Override 414 protected boolean hasConflictingGestures() { 415 return mStatusBar.getBarState() != StatusBarState.SHADE; 416 } 417 418 private boolean onQsTouch(MotionEvent event) { 419 int pointerIndex = event.findPointerIndex(mTrackingPointer); 420 if (pointerIndex < 0) { 421 pointerIndex = 0; 422 mTrackingPointer = event.getPointerId(pointerIndex); 423 } 424 final float y = event.getY(pointerIndex); 425 final float x = event.getX(pointerIndex); 426 427 switch (event.getActionMasked()) { 428 case MotionEvent.ACTION_DOWN: 429 mQsTracking = true; 430 mInitialTouchY = y; 431 mInitialTouchX = x; 432 onQsExpansionStarted(); 433 mInitialHeightOnTouch = mQsExpansionHeight; 434 initVelocityTracker(); 435 trackMovement(event); 436 break; 437 438 case MotionEvent.ACTION_POINTER_UP: 439 final int upPointer = event.getPointerId(event.getActionIndex()); 440 if (mTrackingPointer == upPointer) { 441 // gesture is ongoing, find a new pointer to track 442 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 443 final float newY = event.getY(newIndex); 444 final float newX = event.getX(newIndex); 445 mTrackingPointer = event.getPointerId(newIndex); 446 mInitialHeightOnTouch = mQsExpansionHeight; 447 mInitialTouchY = newY; 448 mInitialTouchX = newX; 449 } 450 break; 451 452 case MotionEvent.ACTION_MOVE: 453 final float h = y - mInitialTouchY; 454 setQsExpansion(h + mInitialHeightOnTouch); 455 trackMovement(event); 456 break; 457 458 case MotionEvent.ACTION_UP: 459 case MotionEvent.ACTION_CANCEL: 460 mQsTracking = false; 461 mTrackingPointer = -1; 462 trackMovement(event); 463 flingQsWithCurrentVelocity(); 464 if (mVelocityTracker != null) { 465 mVelocityTracker.recycle(); 466 mVelocityTracker = null; 467 } 468 break; 469 } 470 return true; 471 } 472 473 @Override 474 public void onOverscrolled(int amount) { 475 if (mIntercepting) { 476 onQsExpansionStarted(amount); 477 mInitialHeightOnTouch = mQsExpansionHeight; 478 mInitialTouchY = mLastTouchY; 479 mInitialTouchX = mLastTouchX; 480 mQsTracking = true; 481 } 482 } 483 484 private void onQsExpansionStarted() { 485 onQsExpansionStarted(0); 486 } 487 488 private void onQsExpansionStarted(int overscrollAmount) { 489 cancelAnimation(); 490 491 // Reset scroll position and apply that position to the expanded height. 492 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 493 mScrollView.scrollTo(0, 0); 494 setQsExpansion(height); 495 } 496 497 private void setQsExpanded(boolean expanded) { 498 boolean changed = mQsExpanded != expanded; 499 if (changed) { 500 mQsExpanded = expanded; 501 updateQsState(); 502 } 503 } 504 505 public void setKeyguardShowing(boolean keyguardShowing) { 506 mKeyguardShowing = keyguardShowing; 507 updateQsState(); 508 } 509 510 private void updateQsState() { 511 mHeader.setExpanded(mQsExpanded); 512 mNotificationStackScroller.setEnabled(!mQsExpanded); 513 mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE); 514 mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded 515 ? View.INVISIBLE 516 : View.VISIBLE); 517 mScrollView.setTouchEnabled(mQsExpanded); 518 } 519 520 private void setQsExpansion(float height) { 521 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 522 if (height > mQsMinExpansionHeight && !mQsExpanded) { 523 setQsExpanded(true); 524 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 525 setQsExpanded(false); 526 } 527 mQsExpansionHeight = height; 528 mHeader.setExpansion(height - mQsPeekHeight); 529 setQsTranslation(height); 530 setQsStackScrollerPadding(height); 531 mStatusBar.userActivity(); 532 } 533 534 private void setQsTranslation(float height) { 535 mQsContainer.setY(height - mQsContainer.getHeight()); 536 } 537 538 private void setQsStackScrollerPadding(float height) { 539 float start = height - mScrollView.getScrollY() + mNotificationTopPadding; 540 float stackHeight = mNotificationStackScroller.getHeight() - start; 541 if (stackHeight <= mMinStackHeight) { 542 float overflow = mMinStackHeight - stackHeight; 543 stackHeight = mMinStackHeight; 544 start = mNotificationStackScroller.getHeight() - stackHeight; 545 mNotificationStackScroller.setTranslationY(overflow); 546 mNotificationTranslation = overflow + mScrollView.getScrollY(); 547 } else { 548 mNotificationStackScroller.setTranslationY(0); 549 mNotificationTranslation = mScrollView.getScrollY(); 550 } 551 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false); 552 } 553 554 private int clampQsStackScrollerPadding(int desiredPadding) { 555 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding); 556 } 557 558 private void trackMovement(MotionEvent event) { 559 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 560 mLastTouchX = event.getX(); 561 mLastTouchY = event.getY(); 562 } 563 564 private void initVelocityTracker() { 565 if (mVelocityTracker != null) { 566 mVelocityTracker.recycle(); 567 } 568 mVelocityTracker = VelocityTracker.obtain(); 569 } 570 571 private float getCurrentVelocity() { 572 if (mVelocityTracker == null) { 573 return 0; 574 } 575 mVelocityTracker.computeCurrentVelocity(1000); 576 return mVelocityTracker.getYVelocity(); 577 } 578 579 private void cancelAnimation() { 580 if (mQsExpansionAnimator != null) { 581 mQsExpansionAnimator.cancel(); 582 } 583 } 584 private void flingSettings(float vel, boolean expand) { 585 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 586 if (target == mQsExpansionHeight) { 587 return; 588 } 589 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 590 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 591 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 592 @Override 593 public void onAnimationUpdate(ValueAnimator animation) { 594 setQsExpansion((Float) animation.getAnimatedValue()); 595 } 596 }); 597 animator.addListener(new AnimatorListenerAdapter() { 598 @Override 599 public void onAnimationEnd(Animator animation) { 600 mQsExpansionAnimator = null; 601 } 602 }); 603 animator.start(); 604 mQsExpansionAnimator = animator; 605 } 606 607 /** 608 * @return Whether we should intercept a gesture to open Quick Settings. 609 */ 610 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 611 if (!mQsExpansionEnabled) { 612 return false; 613 } 614 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight() 615 && y >= mHeader.getTop() && y <= mHeader.getBottom(); 616 if (mQsExpanded) { 617 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); 618 } else { 619 return onHeader; 620 } 621 } 622 623 @Override 624 public void setVisibility(int visibility) { 625 int oldVisibility = getVisibility(); 626 super.setVisibility(visibility); 627 if (visibility != oldVisibility) { 628 reparentStatusIcons(visibility == VISIBLE); 629 } 630 } 631 632 /** 633 * When the notification panel gets expanded, we need to move the status icons in the header 634 * card. 635 */ 636 private void reparentStatusIcons(boolean toHeader) { 637 if (mStatusBar == null) { 638 return; 639 } 640 LinearLayout systemIcons = mStatusBar.getSystemIcons(); 641 if (systemIcons.getParent() != null) { 642 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons); 643 } 644 if (toHeader) { 645 mHeader.attachSystemIcons(systemIcons); 646 } else { 647 mHeader.onSystemIconsDetached(); 648 mStatusBar.reattachSystemIcons(); 649 } 650 } 651 652 @Override 653 protected boolean isScrolledToBottom() { 654 if (!isInSettings()) { 655 return mNotificationStackScroller.isScrolledToBottom(); 656 } 657 return super.isScrolledToBottom(); 658 } 659 660 @Override 661 protected int getMaxPanelHeight() { 662 // TODO: Figure out transition for collapsing when QS is open, adjust height here. 663 int maxPanelHeight = super.getMaxPanelHeight(); 664 int emptyBottomMargin = mStackScrollerContainer.getHeight() 665 - mNotificationStackScroller.getHeight() 666 + mNotificationStackScroller.getEmptyBottomMargin(); 667 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment; 668 maxHeight = Math.max(maxHeight, mStatusBarMinHeight); 669 return maxHeight; 670 } 671 672 private boolean isInSettings() { 673 return mQsExpanded; 674 } 675 676 @Override 677 protected void onHeightUpdated(float expandedHeight) { 678 if (!mQsExpanded) { 679 positionClockAndNotifications(); 680 } 681 mNotificationStackScroller.setStackHeight(expandedHeight); 682 } 683 684 @Override 685 protected void onExpandingStarted() { 686 super.onExpandingStarted(); 687 mNotificationStackScroller.onExpansionStarted(); 688 mIsExpanding = true; 689 } 690 691 @Override 692 protected void onExpandingFinished() { 693 super.onExpandingFinished(); 694 mNotificationStackScroller.onExpansionStopped(); 695 mIsExpanding = false; 696 } 697 698 @Override 699 protected void onOverExpansionChanged(float overExpansion) { 700 float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true); 701 mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + overExpansion 702 - mOverExpansion, true /* onTop */, false /* animate */); 703 super.onOverExpansionChanged(overExpansion); 704 } 705 706 @Override 707 protected void onTrackingStopped(boolean expand) { 708 super.onTrackingStopped(expand); 709 mOverExpansion = 0.0f; 710 mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, 711 true /* animate */); 712 } 713 714 715 @Override 716 public void onHeightChanged(ExpandableView view) { 717 requestPanelHeightUpdate(); 718 } 719 720 @Override 721 public void onScrollChanged() { 722 if (mQsExpanded) { 723 mNotificationStackScroller.setTranslationY( 724 mNotificationTranslation - mScrollView.getScrollY()); 725 } 726 } 727 728 @Override 729 protected void onConfigurationChanged(Configuration newConfig) { 730 super.onConfigurationChanged(newConfig); 731 mPageSwiper.onConfigurationChanged(); 732 } 733 734 @Override 735 public void onClick(View v) { 736 if (v == mHeader.getBackgroundView()) { 737 onQsExpansionStarted(); 738 if (mQsExpanded) { 739 flingSettings(0 /* vel */, false /* expand */); 740 } else if (mQsExpansionEnabled) { 741 flingSettings(0 /* vel */, true /* expand */); 742 } 743 } 744 } 745 746 @Override 747 public void onAnimationToSideStarted(boolean rightPage) { 748 if (rightPage) { 749 mKeyguardBottomArea.launchCamera(); 750 } else { 751 mKeyguardBottomArea.launchPhone(); 752 } 753 mBlockTouches = true; 754 } 755 756 757 @Override 758 public float getPageWidth() { 759 return getWidth(); 760 } 761 762 @Override 763 public ArrayList<View> getTranslationViews() { 764 return mSwipeTranslationViews; 765 } 766 767 @Override 768 public View getLeftIcon() { 769 return mKeyguardBottomArea.getPhoneImageView(); 770 } 771 772 @Override 773 public View getCenterIcon() { 774 return mKeyguardBottomArea.getLockIcon(); 775 } 776 777 @Override 778 public View getRightIcon() { 779 return mKeyguardBottomArea.getCameraImageView(); 780 } 781} 782