1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.stack; 18 19 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator 20 .ExpandAnimationParameters; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ObjectAnimator; 25 import android.animation.PropertyValuesHolder; 26 import android.animation.TimeAnimator; 27 import android.animation.ValueAnimator; 28 import android.animation.ValueAnimator.AnimatorUpdateListener; 29 import android.annotation.FloatRange; 30 import android.annotation.Nullable; 31 import android.content.Context; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.graphics.Canvas; 35 import android.graphics.Color; 36 import android.graphics.Paint; 37 import android.graphics.Path; 38 import android.graphics.PointF; 39 import android.graphics.PorterDuff; 40 import android.graphics.PorterDuffXfermode; 41 import android.graphics.Rect; 42 import android.os.Bundle; 43 import android.os.Handler; 44 import android.service.notification.StatusBarNotification; 45 import android.support.annotation.NonNull; 46 import android.support.annotation.VisibleForTesting; 47 import android.support.v4.graphics.ColorUtils; 48 import android.util.AttributeSet; 49 import android.util.FloatProperty; 50 import android.util.Log; 51 import android.util.MathUtils; 52 import android.util.Pair; 53 import android.util.Property; 54 import android.view.ContextThemeWrapper; 55 import android.view.InputDevice; 56 import android.view.MotionEvent; 57 import android.view.VelocityTracker; 58 import android.view.View; 59 import android.view.ViewConfiguration; 60 import android.view.ViewGroup; 61 import android.view.ViewTreeObserver; 62 import android.view.WindowInsets; 63 import android.view.accessibility.AccessibilityEvent; 64 import android.view.accessibility.AccessibilityNodeInfo; 65 import android.view.animation.AnimationUtils; 66 import android.view.animation.Interpolator; 67 import android.widget.OverScroller; 68 import android.widget.ScrollView; 69 70 import com.android.internal.logging.MetricsLogger; 71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 72 import com.android.keyguard.KeyguardSliceView; 73 import com.android.settingslib.Utils; 74 import com.android.systemui.Dependency; 75 import com.android.systemui.ExpandHelper; 76 import com.android.systemui.Interpolators; 77 import com.android.systemui.R; 78 import com.android.systemui.SwipeHelper; 79 import com.android.systemui.classifier.FalsingManager; 80 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 81 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; 82 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; 83 import com.android.systemui.statusbar.ActivatableNotificationView; 84 import com.android.systemui.statusbar.EmptyShadeView; 85 import com.android.systemui.statusbar.ExpandableNotificationRow; 86 import com.android.systemui.statusbar.ExpandableView; 87 import com.android.systemui.statusbar.FooterView; 88 import com.android.systemui.statusbar.NotificationBlockingHelperManager; 89 import com.android.systemui.statusbar.NotificationData; 90 import com.android.systemui.statusbar.NotificationGuts; 91 import com.android.systemui.statusbar.NotificationListContainer; 92 import com.android.systemui.statusbar.NotificationLogger; 93 import com.android.systemui.statusbar.NotificationShelf; 94 import com.android.systemui.statusbar.NotificationSnooze; 95 import com.android.systemui.statusbar.StackScrollerDecorView; 96 import com.android.systemui.statusbar.StatusBarState; 97 import com.android.systemui.statusbar.notification.FakeShadowView; 98 import com.android.systemui.statusbar.notification.NotificationUtils; 99 import com.android.systemui.statusbar.notification.VisibilityLocationProvider; 100 import com.android.systemui.statusbar.phone.DozeParameters; 101 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; 102 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; 103 import com.android.systemui.statusbar.phone.NotificationGroupManager; 104 import com.android.systemui.statusbar.phone.ScrimController; 105 import com.android.systemui.statusbar.phone.StatusBar; 106 import com.android.systemui.statusbar.policy.HeadsUpUtil; 107 import com.android.systemui.statusbar.policy.ScrollAdapter; 108 109 import java.io.FileDescriptor; 110 import java.io.PrintWriter; 111 import java.util.ArrayList; 112 import java.util.Collections; 113 import java.util.Comparator; 114 import java.util.HashSet; 115 import java.util.List; 116 import java.util.function.BiConsumer; 117 118 /** 119 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 120 */ 121 public class NotificationStackScrollLayout extends ViewGroup 122 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, 123 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, 124 NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider, 125 NotificationListContainer { 126 127 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; 128 private static final String TAG = "StackScroller"; 129 private static final boolean DEBUG = false; 130 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 131 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 132 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 133 /** 134 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 135 */ 136 private static final int INVALID_POINTER = -1; 137 138 private ExpandHelper mExpandHelper; 139 private NotificationSwipeHelper mSwipeHelper; 140 private boolean mSwipingInProgress; 141 private int mCurrentStackHeight = Integer.MAX_VALUE; 142 private final Paint mBackgroundPaint = new Paint(); 143 private final Path mBackgroundPath = new Path(); 144 private final boolean mShouldDrawNotificationBackground; 145 146 private float mExpandedHeight; 147 private int mOwnScrollY; 148 private int mMaxLayoutHeight; 149 150 private VelocityTracker mVelocityTracker; 151 private OverScroller mScroller; 152 private Runnable mFinishScrollingCallback; 153 private int mTouchSlop; 154 private int mMinimumVelocity; 155 private int mMaximumVelocity; 156 private int mOverflingDistance; 157 private float mMaxOverScroll; 158 private boolean mIsBeingDragged; 159 private int mLastMotionY; 160 private int mDownX; 161 private int mActivePointerId = INVALID_POINTER; 162 private boolean mTouchIsClick; 163 private float mInitialTouchX; 164 private float mInitialTouchY; 165 166 private Paint mDebugPaint; 167 private int mContentHeight; 168 private int mIntrinsicContentHeight; 169 private int mCollapsedSize; 170 private int mPaddingBetweenElements; 171 private int mIncreasedPaddingBetweenElements; 172 private int mMaxTopPadding; 173 private int mRegularTopPadding; 174 private int mDarkTopPadding; 175 // Current padding, will be either mRegularTopPadding or mDarkTopPadding 176 private int mTopPadding; 177 // Distance between AOD separator and shelf 178 private int mDarkSeparatorPadding; 179 private int mBottomMargin; 180 private int mBottomInset = 0; 181 private float mQsExpansionFraction; 182 183 /** 184 * The algorithm which calculates the properties for our children 185 */ 186 protected final StackScrollAlgorithm mStackScrollAlgorithm; 187 188 /** 189 * The current State this Layout is in 190 */ 191 private StackScrollState mCurrentStackScrollState = new StackScrollState(this); 192 private final AmbientState mAmbientState; 193 private NotificationGroupManager mGroupManager; 194 private HashSet<View> mChildrenToAddAnimated = new HashSet<>(); 195 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); 196 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>(); 197 private ArrayList<View> mSnappedBackChildren = new ArrayList<>(); 198 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>(); 199 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>(); 200 private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); 201 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); 202 private ArrayList<View> mSwipedOutViews = new ArrayList<>(); 203 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 204 private boolean mAnimationsEnabled; 205 private boolean mChangePositionInProgress; 206 private boolean mChildTransferInProgress; 207 208 /** 209 * The raw amount of the overScroll on the top, which is not rubber-banded. 210 */ 211 private float mOverScrolledTopPixels; 212 213 /** 214 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 215 */ 216 private float mOverScrolledBottomPixels; 217 private NotificationLogger.OnChildLocationsChangedListener mListener; 218 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 219 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 220 private OnEmptySpaceClickListener mOnEmptySpaceClickListener; 221 private boolean mNeedsAnimation; 222 private boolean mTopPaddingNeedsAnimation; 223 private boolean mDimmedNeedsAnimation; 224 private boolean mHideSensitiveNeedsAnimation; 225 private boolean mDarkNeedsAnimation; 226 private int mDarkAnimationOriginIndex; 227 private boolean mActivateNeedsAnimation; 228 private boolean mGoToFullShadeNeedsAnimation; 229 private boolean mIsExpanded = true; 230 private boolean mChildrenUpdateRequested; 231 private boolean mIsExpansionChanging; 232 private boolean mPanelTracking; 233 private boolean mExpandingNotification; 234 private boolean mExpandedInThisMotion; 235 private boolean mShouldShowShelfOnly; 236 protected boolean mScrollingEnabled; 237 protected FooterView mFooterView; 238 protected EmptyShadeView mEmptyShadeView; 239 private boolean mDismissAllInProgress; 240 private boolean mFadeNotificationsOnDismiss; 241 242 /** 243 * Was the scroller scrolled to the top when the down motion was observed? 244 */ 245 private boolean mScrolledToTopOnFirstDown; 246 /** 247 * The minimal amount of over scroll which is needed in order to switch to the quick settings 248 * when over scrolling on a expanded card. 249 */ 250 private float mMinTopOverScrollToEscape; 251 private int mIntrinsicPadding; 252 private float mStackTranslation; 253 private float mTopPaddingOverflow; 254 private boolean mDontReportNextOverScroll; 255 private boolean mDontClampNextScroll; 256 private boolean mNeedViewResizeAnimation; 257 private View mExpandedGroupView; 258 private boolean mEverythingNeedsAnimation; 259 260 /** 261 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 262 * This is needed to avoid scrolling too far after the notification was collapsed in the same 263 * motion. 264 */ 265 private int mMaxScrollAfterExpand; 266 private ExpandableNotificationRow.LongPressListener mLongPressListener; 267 268 private NotificationMenuRowPlugin mCurrMenuRow; 269 private View mTranslatingParentView; 270 private View mMenuExposedView; 271 boolean mCheckForLeavebehind; 272 273 /** 274 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 275 * animating. 276 */ 277 private boolean mOnlyScrollingInThisMotion; 278 private boolean mDisallowDismissInThisMotion; 279 private boolean mDisallowScrollingInThisMotion; 280 private long mGoToFullShadeDelay; 281 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 282 = new ViewTreeObserver.OnPreDrawListener() { 283 @Override 284 public boolean onPreDraw() { 285 updateForcedScroll(); 286 updateChildren(); 287 mChildrenUpdateRequested = false; 288 getViewTreeObserver().removeOnPreDrawListener(this); 289 return true; 290 } 291 }; 292 private StatusBar mStatusBar; 293 private int[] mTempInt2 = new int[2]; 294 private boolean mGenerateChildOrderChangedEvent; 295 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); 296 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>(); 297 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations 298 = new HashSet<>(); 299 private HeadsUpManagerPhone mHeadsUpManager; 300 private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager(); 301 private boolean mTrackingHeadsUp; 302 private ScrimController mScrimController; 303 private boolean mForceNoOverlappingRendering; 304 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>(); 305 private FalsingManager mFalsingManager; 306 private boolean mAnimationRunning; 307 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater 308 = new ViewTreeObserver.OnPreDrawListener() { 309 @Override 310 public boolean onPreDraw() { 311 onPreDrawDuringAnimation(); 312 return true; 313 } 314 }; 315 private Rect mBackgroundBounds = new Rect(); 316 private Rect mStartAnimationRect = new Rect(); 317 private Rect mEndAnimationRect = new Rect(); 318 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1); 319 private boolean mAnimateNextBackgroundBottom; 320 private boolean mAnimateNextBackgroundTop; 321 private ObjectAnimator mBottomAnimator = null; 322 private ObjectAnimator mTopAnimator = null; 323 private ActivatableNotificationView mFirstVisibleBackgroundChild = null; 324 private ActivatableNotificationView mLastVisibleBackgroundChild = null; 325 private int mBgColor; 326 private float mDimAmount; 327 private ValueAnimator mDimAnimator; 328 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); 329 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { 330 @Override 331 public void onAnimationEnd(Animator animation) { 332 mDimAnimator = null; 333 } 334 }; 335 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener 336 = new ValueAnimator.AnimatorUpdateListener() { 337 338 @Override 339 public void onAnimationUpdate(ValueAnimator animation) { 340 setDimAmount((Float) animation.getAnimatedValue()); 341 } 342 }; 343 protected ViewGroup mQsContainer; 344 private boolean mContinuousShadowUpdate; 345 private ViewTreeObserver.OnPreDrawListener mShadowUpdater 346 = new ViewTreeObserver.OnPreDrawListener() { 347 348 @Override 349 public boolean onPreDraw() { 350 updateViewShadows(); 351 return true; 352 } 353 }; 354 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() { 355 @Override 356 public int compare(ExpandableView view, ExpandableView otherView) { 357 float endY = view.getTranslationY() + view.getActualHeight(); 358 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight(); 359 if (endY < otherEndY) { 360 return -1; 361 } else if (endY > otherEndY) { 362 return 1; 363 } else { 364 // The two notifications end at the same location 365 return 0; 366 } 367 } 368 }; 369 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); 370 private boolean mPulsing; 371 private boolean mDrawBackgroundAsSrc; 372 private boolean mFadingOut; 373 private boolean mParentNotFullyVisible; 374 private boolean mGroupExpandedForMeasure; 375 private boolean mScrollable; 376 private View mForcedScroll; 377 private View mNeedingPulseAnimation; 378 private float mDarkAmount = 0f; 379 private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT = 380 new FloatProperty<NotificationStackScrollLayout>("darkAmount") { 381 @Override 382 public void setValue(NotificationStackScrollLayout object, float value) { 383 object.setDarkAmount(value); 384 } 385 386 @Override 387 public Float get(NotificationStackScrollLayout object) { 388 return object.getDarkAmount(); 389 } 390 }; 391 private ObjectAnimator mDarkAmountAnimator; 392 private boolean mUsingLightTheme; 393 private boolean mQsExpanded; 394 private boolean mForwardScrollable; 395 private boolean mBackwardScrollable; 396 private NotificationShelf mShelf; 397 private int mMaxDisplayedNotifications = -1; 398 private int mStatusBarHeight; 399 private int mMinInteractionHeight; 400 private boolean mNoAmbient; 401 private final Rect mClipRect = new Rect(); 402 private boolean mIsClipped; 403 private Rect mRequestedClipBounds; 404 private boolean mInHeadsUpPinnedMode; 405 private boolean mHeadsUpAnimatingAway; 406 private int mStatusBarState; 407 private int mCachedBackgroundColor; 408 private boolean mHeadsUpGoingAwayAnimationsAllowed = true; 409 private Runnable mAnimateScroll = this::animateScroll; 410 private int mCornerRadius; 411 private int mSidePaddings; 412 private final int mSeparatorWidth; 413 private final int mSeparatorThickness; 414 private final Rect mBackgroundAnimationRect = new Rect(); 415 private int mAntiBurnInOffsetX; 416 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); 417 private int mHeadsUpInset; 418 private HeadsUpAppearanceController mHeadsUpAppearanceController; 419 420 public NotificationStackScrollLayout(Context context) { 421 this(context, null); 422 } 423 424 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 425 this(context, attrs, 0); 426 } 427 428 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 429 this(context, attrs, defStyleAttr, 0); 430 } 431 432 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 433 int defStyleRes) { 434 super(context, attrs, defStyleAttr, defStyleRes); 435 Resources res = getResources(); 436 437 mAmbientState = new AmbientState(context); 438 mBgColor = context.getColor(R.color.notification_shade_background_color); 439 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); 440 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); 441 mExpandHelper = new ExpandHelper(getContext(), this, 442 minHeight, maxHeight); 443 mExpandHelper.setEventSource(this); 444 mExpandHelper.setScrollAdapter(this); 445 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); 446 mStackScrollAlgorithm = createStackScrollAlgorithm(context); 447 initView(context); 448 mFalsingManager = FalsingManager.getInstance(context); 449 mShouldDrawNotificationBackground = 450 res.getBoolean(R.bool.config_drawNotificationBackground); 451 mFadeNotificationsOnDismiss = 452 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); 453 mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width); 454 mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness); 455 mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); 456 mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); 457 mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); 458 addOnExpandedHeightListener(mRoundnessManager::setExpanded); 459 460 // Blocking helper manager wants to know the expanded state, update as well. 461 NotificationBlockingHelperManager blockingHelperManager = 462 Dependency.get(NotificationBlockingHelperManager.class); 463 addOnExpandedHeightListener((height, unused) -> { 464 blockingHelperManager.setNotificationShadeExpanded(height); 465 }); 466 467 updateWillNotDraw(); 468 mBackgroundPaint.setAntiAlias(true); 469 if (DEBUG) { 470 mDebugPaint = new Paint(); 471 mDebugPaint.setColor(0xffff0000); 472 mDebugPaint.setStrokeWidth(2); 473 mDebugPaint.setStyle(Paint.Style.STROKE); 474 } 475 } 476 477 @Override 478 public NotificationSwipeActionHelper getSwipeActionHelper() { 479 return mSwipeHelper; 480 } 481 482 @Override 483 public void onMenuClicked(View view, int x, int y, MenuItem item) { 484 if (mLongPressListener == null) { 485 return; 486 } 487 if (view instanceof ExpandableNotificationRow) { 488 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 489 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR, 490 row.getStatusBarNotification().getPackageName()); 491 } 492 mLongPressListener.onLongPress(view, x, y, item); 493 } 494 495 @Override 496 public void onMenuReset(View row) { 497 if (mTranslatingParentView != null && row == mTranslatingParentView) { 498 mMenuExposedView = null; 499 mTranslatingParentView = null; 500 } 501 } 502 503 @Override 504 public void onMenuShown(View row) { 505 mMenuExposedView = mTranslatingParentView; 506 if (row instanceof ExpandableNotificationRow) { 507 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, 508 ((ExpandableNotificationRow) row).getStatusBarNotification() 509 .getPackageName()); 510 } 511 mSwipeHelper.onMenuShown(row); 512 } 513 514 protected void onDraw(Canvas canvas) { 515 if (mShouldDrawNotificationBackground 516 && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) { 517 drawBackground(canvas); 518 } 519 520 if (DEBUG) { 521 int y = mTopPadding; 522 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 523 y = getLayoutHeight(); 524 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 525 y = getHeight() - getEmptyBottomMargin(); 526 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 527 } 528 } 529 530 private void drawBackground(Canvas canvas) { 531 final int lockScreenLeft = mSidePaddings; 532 final int lockScreenRight = getWidth() - mSidePaddings; 533 final int lockScreenTop = mCurrentBounds.top; 534 final int lockScreenBottom = mCurrentBounds.bottom; 535 final int darkLeft = getWidth() / 2 - mSeparatorWidth / 2; 536 final int darkRight = darkLeft + mSeparatorWidth; 537 final int darkTop = (int) (mRegularTopPadding + mSeparatorThickness / 2f); 538 final int darkBottom = darkTop + mSeparatorThickness; 539 540 if (mAmbientState.hasPulsingNotifications()) { 541 // No divider, we have a notification icon instead 542 } else if (mAmbientState.isFullyDark()) { 543 // Only draw divider on AOD if we actually have notifications 544 if (mFirstVisibleBackgroundChild != null) { 545 canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint); 546 } 547 } else { 548 float inverseDark = 1 - mDarkAmount; 549 float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark); 550 float xProgress = Interpolators.FAST_OUT_SLOW_IN 551 .getInterpolation(inverseDark * 2f); 552 553 mBackgroundAnimationRect.set( 554 (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress), 555 (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress), 556 (int) MathUtils.lerp(darkRight, lockScreenRight, xProgress), 557 (int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress)); 558 if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) { 559 canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top, 560 mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom, 561 mCornerRadius, mCornerRadius, mBackgroundPaint); 562 } 563 } 564 updateClipping(); 565 } 566 567 private void updateBackgroundDimming() { 568 // No need to update the background color if it's not being drawn. 569 if (!mShouldDrawNotificationBackground) { 570 return; 571 } 572 573 float alpha = 574 BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); 575 alpha *= 1f - mDarkAmount; 576 // We need to manually blend in the background color. 577 int scrimColor = mScrimController.getBackgroundColor(); 578 int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); 579 580 // Interpolate between semi-transparent notification panel background color 581 // and white AOD separator. 582 float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(mDarkAmount); 583 int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation); 584 585 if (mCachedBackgroundColor != color) { 586 mCachedBackgroundColor = color; 587 mBackgroundPaint.setColor(color); 588 invalidate(); 589 } 590 } 591 592 private void initView(Context context) { 593 mScroller = new OverScroller(getContext()); 594 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 595 setClipChildren(false); 596 final ViewConfiguration configuration = ViewConfiguration.get(context); 597 mTouchSlop = configuration.getScaledTouchSlop(); 598 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 599 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 600 mOverflingDistance = configuration.getScaledOverflingDistance(); 601 602 Resources res = context.getResources(); 603 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); 604 mStackScrollAlgorithm.initView(context); 605 mAmbientState.reload(context); 606 mPaddingBetweenElements = Math.max(1, 607 res.getDimensionPixelSize(R.dimen.notification_divider_height)); 608 mIncreasedPaddingBetweenElements = 609 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased); 610 mMinTopOverScrollToEscape = res.getDimensionPixelSize( 611 R.dimen.min_top_overscroll_to_qs); 612 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); 613 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); 614 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); 615 mMinInteractionHeight = res.getDimensionPixelSize( 616 R.dimen.notification_min_interaction_height); 617 mCornerRadius = res.getDimensionPixelSize( 618 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); 619 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( 620 R.dimen.heads_up_status_bar_padding); 621 } 622 623 public void setDrawBackgroundAsSrc(boolean asSrc) { 624 mDrawBackgroundAsSrc = asSrc; 625 updateSrcDrawing(); 626 } 627 628 private void updateSrcDrawing() { 629 if (!mShouldDrawNotificationBackground) { 630 return; 631 } 632 633 mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible 634 ? mSrcMode : null); 635 invalidate(); 636 } 637 638 private void notifyHeightChangeListener(ExpandableView view) { 639 notifyHeightChangeListener(view, false /* needsAnimation */); 640 } 641 642 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) { 643 if (mOnHeightChangedListener != null) { 644 mOnHeightChangedListener.onHeightChanged(view, needsAnimation); 645 } 646 } 647 648 @Override 649 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 650 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 651 652 int width = MeasureSpec.getSize(widthMeasureSpec); 653 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2, 654 MeasureSpec.getMode(widthMeasureSpec)); 655 // We need to measure all children even the GONE ones, such that the heights are calculated 656 // correctly as they are used to calculate how many we can fit on the screen. 657 final int size = getChildCount(); 658 for (int i = 0; i < size; i++) { 659 measureChild(getChildAt(i), childWidthSpec, heightMeasureSpec); 660 } 661 } 662 663 @Override 664 protected void onLayout(boolean changed, int l, int t, int r, int b) { 665 // we layout all our children centered on the top 666 float centerX = getWidth() / 2.0f; 667 for (int i = 0; i < getChildCount(); i++) { 668 View child = getChildAt(i); 669 // We need to layout all children even the GONE ones, such that the heights are 670 // calculated correctly as they are used to calculate how many we can fit on the screen 671 float width = child.getMeasuredWidth(); 672 float height = child.getMeasuredHeight(); 673 child.layout((int) (centerX - width / 2.0f), 674 0, 675 (int) (centerX + width / 2.0f), 676 (int) height); 677 } 678 setMaxLayoutHeight(getHeight()); 679 updateContentHeight(); 680 clampScrollPosition(); 681 requestChildrenUpdate(); 682 updateFirstAndLastBackgroundViews(); 683 updateAlgorithmLayoutMinHeight(); 684 } 685 686 private void requestAnimationOnViewResize(ExpandableNotificationRow row) { 687 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) { 688 mNeedViewResizeAnimation = true; 689 mNeedsAnimation = true; 690 } 691 } 692 693 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) { 694 mAmbientState.setSpeedBumpIndex(newIndex); 695 mNoAmbient = noAmbient; 696 } 697 698 @Override 699 public void setChildLocationsChangedListener( 700 NotificationLogger.OnChildLocationsChangedListener listener) { 701 mListener = listener; 702 } 703 704 @Override 705 public boolean isInVisibleLocation(ExpandableNotificationRow row) { 706 ExpandableViewState childViewState = mCurrentStackScrollState.getViewStateForView(row); 707 if (childViewState == null) { 708 return false; 709 } 710 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) { 711 return false; 712 } 713 if (row.getVisibility() != View.VISIBLE) { 714 return false; 715 } 716 return true; 717 } 718 719 private void setMaxLayoutHeight(int maxLayoutHeight) { 720 mMaxLayoutHeight = maxLayoutHeight; 721 mShelf.setMaxLayoutHeight(maxLayoutHeight); 722 updateAlgorithmHeightAndPadding(); 723 } 724 725 private void updateAlgorithmHeightAndPadding() { 726 mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding, mDarkAmount); 727 mAmbientState.setLayoutHeight(getLayoutHeight()); 728 updateAlgorithmLayoutMinHeight(); 729 mAmbientState.setTopPadding(mTopPadding); 730 } 731 732 private void updateAlgorithmLayoutMinHeight() { 733 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition() 734 ? getLayoutMinHeight() : 0); 735 } 736 737 /** 738 * Updates the children views according to the stack scroll algorithm. Call this whenever 739 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 740 */ 741 private void updateChildren() { 742 updateScrollStateForAddedChildren(); 743 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() 744 ? 0 745 : mScroller.getCurrVelocity()); 746 mAmbientState.setScrollY(mOwnScrollY); 747 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 748 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 749 applyCurrentState(); 750 } else { 751 startAnimationToState(); 752 } 753 } 754 755 private void onPreDrawDuringAnimation() { 756 mShelf.updateAppearance(); 757 updateClippingToTopRoundedCorner(); 758 if (!mNeedsAnimation && !mChildrenUpdateRequested) { 759 updateBackground(); 760 } 761 } 762 763 private void updateClippingToTopRoundedCorner() { 764 Float clipStart = (float) mTopPadding 765 + mStackTranslation 766 + mAmbientState.getExpandAnimationTopChange(); 767 Float clipEnd = clipStart + mCornerRadius; 768 boolean first = true; 769 for (int i = 0; i < getChildCount(); i++) { 770 ExpandableView child = (ExpandableView) getChildAt(i); 771 if (child.getVisibility() == GONE) { 772 continue; 773 } 774 float start = child.getTranslationY(); 775 float end = start + child.getActualHeight(); 776 boolean clip = clipStart > start && clipStart < end 777 || clipEnd >= start && clipEnd <= end; 778 clip &= !(first && mOwnScrollY == 0); 779 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) 780 : ExpandableView.NO_ROUNDNESS); 781 first = false; 782 } 783 } 784 785 private void updateScrollStateForAddedChildren() { 786 if (mChildrenToAddAnimated.isEmpty()) { 787 return; 788 } 789 for (int i = 0; i < getChildCount(); i++) { 790 ExpandableView child = (ExpandableView) getChildAt(i); 791 if (mChildrenToAddAnimated.contains(child)) { 792 int startingPosition = getPositionInLinearLayout(child); 793 float increasedPaddingAmount = child.getIncreasedPaddingAmount(); 794 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements 795 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements; 796 int childHeight = getIntrinsicHeight(child) + padding; 797 if (startingPosition < mOwnScrollY) { 798 // This child starts off screen, so let's keep it offscreen to keep the others visible 799 800 setOwnScrollY(mOwnScrollY + childHeight); 801 } 802 } 803 } 804 clampScrollPosition(); 805 } 806 807 private void updateForcedScroll() { 808 if (mForcedScroll != null && (!mForcedScroll.hasFocus() 809 || !mForcedScroll.isAttachedToWindow())) { 810 mForcedScroll = null; 811 } 812 if (mForcedScroll != null) { 813 ExpandableView expandableView = (ExpandableView) mForcedScroll; 814 int positionInLinearLayout = getPositionInLinearLayout(expandableView); 815 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 816 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 817 818 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); 819 820 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 821 // that it is not visible anymore. 822 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 823 setOwnScrollY(targetScroll); 824 } 825 } 826 } 827 828 private void requestChildrenUpdate() { 829 if (!mChildrenUpdateRequested) { 830 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 831 mChildrenUpdateRequested = true; 832 invalidate(); 833 } 834 } 835 836 private boolean isCurrentlyAnimating() { 837 return mStateAnimator.isRunning(); 838 } 839 840 private void clampScrollPosition() { 841 int scrollRange = getScrollRange(); 842 if (scrollRange < mOwnScrollY) { 843 setOwnScrollY(scrollRange); 844 } 845 } 846 847 public int getTopPadding() { 848 return mTopPadding; 849 } 850 851 private void setTopPadding(int topPadding, boolean animate) { 852 if (mRegularTopPadding != topPadding) { 853 mRegularTopPadding = topPadding; 854 mDarkTopPadding = topPadding + mDarkSeparatorPadding; 855 mAmbientState.setDarkTopPadding(mDarkTopPadding); 856 updateAlgorithmHeightAndPadding(); 857 updateContentHeight(); 858 if (animate && mAnimationsEnabled && mIsExpanded) { 859 mTopPaddingNeedsAnimation = true; 860 mNeedsAnimation = true; 861 } 862 requestChildrenUpdate(); 863 notifyHeightChangeListener(null, animate); 864 } 865 } 866 867 /** 868 * Update the height of the panel. 869 * 870 * @param height the expanded height of the panel 871 */ 872 public void setExpandedHeight(float height) { 873 mExpandedHeight = height; 874 setIsExpanded(height > 0); 875 int minExpansionHeight = getMinExpansionHeight(); 876 if (height < minExpansionHeight) { 877 mClipRect.left = 0; 878 mClipRect.right = getWidth(); 879 mClipRect.top = 0; 880 mClipRect.bottom = (int) height; 881 height = minExpansionHeight; 882 setRequestedClipBounds(mClipRect); 883 } else { 884 setRequestedClipBounds(null); 885 } 886 int stackHeight; 887 float translationY; 888 float appearEndPosition = getAppearEndPosition(); 889 float appearStartPosition = getAppearStartPosition(); 890 float appearFraction = 1.0f; 891 boolean appearing = height < appearEndPosition; 892 mAmbientState.setAppearing(appearing); 893 if (!appearing) { 894 translationY = 0; 895 if (mShouldShowShelfOnly) { 896 stackHeight = mTopPadding + mShelf.getIntrinsicHeight(); 897 } else if (mQsExpanded) { 898 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding; 899 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight(); 900 if (stackStartPosition <= stackEndPosition) { 901 stackHeight = stackEndPosition; 902 } else { 903 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition, 904 stackEndPosition, mQsExpansionFraction); 905 } 906 } else { 907 stackHeight = (int) height; 908 } 909 } else { 910 appearFraction = getAppearFraction(height); 911 if (appearFraction >= 0) { 912 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, 913 appearFraction); 914 } else { 915 // This may happen when pushing up a heads up. We linearly push it up from the 916 // start 917 translationY = height - appearStartPosition + getExpandTranslationStart(); 918 } 919 if (isHeadsUpTransition()) { 920 stackHeight = mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); 921 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); 922 } else { 923 stackHeight = (int) (height - translationY); 924 } 925 } 926 if (stackHeight != mCurrentStackHeight) { 927 mCurrentStackHeight = stackHeight; 928 updateAlgorithmHeightAndPadding(); 929 requestChildrenUpdate(); 930 } 931 setStackTranslation(translationY); 932 for (int i = 0; i < mExpandedHeightListeners.size(); i++) { 933 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i); 934 listener.accept(mExpandedHeight, appearFraction); 935 } 936 } 937 938 private void setRequestedClipBounds(Rect clipRect) { 939 mRequestedClipBounds = clipRect; 940 updateClipping(); 941 } 942 943 /** 944 * Return the height of the content ignoring the footer. 945 */ 946 public int getIntrinsicContentHeight() { 947 return mIntrinsicContentHeight; 948 } 949 950 public void updateClipping() { 951 boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1; 952 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode 953 && !mHeadsUpAnimatingAway; 954 if (mIsClipped != clipped) { 955 mIsClipped = clipped; 956 updateFadingState(); 957 } 958 959 if (animatingClipping) { 960 setClipBounds(mBackgroundAnimationRect); 961 } else if (clipped) { 962 setClipBounds(mRequestedClipBounds); 963 } else { 964 setClipBounds(null); 965 } 966 } 967 968 /** 969 * @return The translation at the beginning when expanding. 970 * Measured relative to the resting position. 971 */ 972 private float getExpandTranslationStart() { 973 return - mTopPadding; 974 } 975 976 /** 977 * @return the position from where the appear transition starts when expanding. 978 * Measured in absolute height. 979 */ 980 private float getAppearStartPosition() { 981 if (isHeadsUpTransition()) { 982 return mHeadsUpInset + mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); 983 } 984 return getMinExpansionHeight(); 985 } 986 987 /** 988 * @return the height of the top heads up notification when pinned. This is different from the 989 * intrinsic height, which also includes whether the notification is system expanded and 990 * is mainly used when dragging down from a heads up notification. 991 */ 992 private int getTopHeadsUpPinnedHeight() { 993 NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); 994 if (topEntry == null) { 995 return 0; 996 } 997 ExpandableNotificationRow row = topEntry.row; 998 if (row.isChildInGroup()) { 999 final ExpandableNotificationRow groupSummary 1000 = mGroupManager.getGroupSummary(row.getStatusBarNotification()); 1001 if (groupSummary != null) { 1002 row = groupSummary; 1003 } 1004 } 1005 return row.getPinnedHeadsUpHeight(); 1006 } 1007 1008 /** 1009 * @return the position from where the appear transition ends when expanding. 1010 * Measured in absolute height. 1011 */ 1012 private float getAppearEndPosition() { 1013 int appearPosition; 1014 int notGoneChildCount = getNotGoneChildCount(); 1015 if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) { 1016 if (isHeadsUpTransition() 1017 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { 1018 appearPosition = getTopHeadsUpPinnedHeight(); 1019 } else { 1020 appearPosition = 0; 1021 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) { 1022 appearPosition += mShelf.getIntrinsicHeight(); 1023 } 1024 } 1025 } else { 1026 appearPosition = mEmptyShadeView.getHeight(); 1027 } 1028 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); 1029 } 1030 1031 private boolean isHeadsUpTransition() { 1032 return mTrackingHeadsUp && mFirstVisibleBackgroundChild != null 1033 && mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild); 1034 } 1035 1036 /** 1037 * @param height the height of the panel 1038 * @return the fraction of the appear animation that has been performed 1039 */ 1040 public float getAppearFraction(float height) { 1041 float appearEndPosition = getAppearEndPosition(); 1042 float appearStartPosition = getAppearStartPosition(); 1043 return (height - appearStartPosition) 1044 / (appearEndPosition - appearStartPosition); 1045 } 1046 1047 public float getStackTranslation() { 1048 return mStackTranslation; 1049 } 1050 1051 private void setStackTranslation(float stackTranslation) { 1052 if (stackTranslation != mStackTranslation) { 1053 mStackTranslation = stackTranslation; 1054 mAmbientState.setStackTranslation(stackTranslation); 1055 requestChildrenUpdate(); 1056 } 1057 } 1058 1059 /** 1060 * Get the current height of the view. This is at most the msize of the view given by a the 1061 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 1062 * 1063 * @return either the layout height or the externally defined height, whichever is smaller 1064 */ 1065 private int getLayoutHeight() { 1066 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 1067 } 1068 1069 public int getFirstItemMinHeight() { 1070 final ExpandableView firstChild = getFirstChildNotGone(); 1071 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; 1072 } 1073 1074 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { 1075 mLongPressListener = listener; 1076 } 1077 1078 public void setQsContainer(ViewGroup qsContainer) { 1079 mQsContainer = qsContainer; 1080 } 1081 1082 /** 1083 * Handles cleanup after the given {@code view} has been fully swiped out (including 1084 * re-invoking dismiss logic in case the notification has not made its way out yet). 1085 */ 1086 @Override 1087 public void onChildDismissed(View view) { 1088 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1089 if (!row.isDismissed()) { 1090 handleChildViewDismissed(view); 1091 } 1092 ViewGroup transientContainer = row.getTransientContainer(); 1093 if (transientContainer != null) { 1094 transientContainer.removeTransientView(view); 1095 } 1096 } 1097 1098 /** 1099 * Starts up notification dismiss and tells the notification, if any, to remove itself from 1100 * layout. 1101 * 1102 * @param view view (e.g. notification) to dismiss from the layout 1103 */ 1104 private void handleChildViewDismissed(View view) { 1105 if (mDismissAllInProgress) { 1106 return; 1107 } 1108 1109 boolean isBlockingHelperShown = false; 1110 1111 setSwipingInProgress(false); 1112 if (mDragAnimPendingChildren.contains(view)) { 1113 // We start the swipe and finish it in the same frame; we don't want a drag animation. 1114 mDragAnimPendingChildren.remove(view); 1115 } 1116 mAmbientState.onDragFinished(view); 1117 updateContinuousShadowDrawing(); 1118 1119 if (view instanceof ExpandableNotificationRow) { 1120 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1121 if (row.isHeadsUp()) { 1122 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); 1123 } 1124 isBlockingHelperShown = 1125 row.performDismissWithBlockingHelper(false /* fromAccessibility */); 1126 } 1127 1128 if (!isBlockingHelperShown) { 1129 mSwipedOutViews.add(view); 1130 } 1131 mFalsingManager.onNotificationDismissed(); 1132 if (mFalsingManager.shouldEnforceBouncer()) { 1133 mStatusBar.executeRunnableDismissingKeyguard( 1134 null, 1135 null /* cancelAction */, 1136 false /* dismissShade */, 1137 true /* afterKeyguardGone */, 1138 false /* deferred */); 1139 } 1140 } 1141 1142 @Override 1143 public void onChildSnappedBack(View animView, float targetLeft) { 1144 mAmbientState.onDragFinished(animView); 1145 updateContinuousShadowDrawing(); 1146 if (!mDragAnimPendingChildren.contains(animView)) { 1147 if (mAnimationsEnabled) { 1148 mSnappedBackChildren.add(animView); 1149 mNeedsAnimation = true; 1150 } 1151 requestChildrenUpdate(); 1152 } else { 1153 // We start the swipe and snap back in the same frame, we don't want any animation 1154 mDragAnimPendingChildren.remove(animView); 1155 } 1156 if (mCurrMenuRow != null && targetLeft == 0) { 1157 mCurrMenuRow.resetMenu(); 1158 mCurrMenuRow = null; 1159 } 1160 } 1161 1162 @Override 1163 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 1164 // Returning true prevents alpha fading. 1165 return !mFadeNotificationsOnDismiss; 1166 } 1167 1168 @Override 1169 public void onBeginDrag(View v) { 1170 mFalsingManager.onNotificatonStartDismissing(); 1171 setSwipingInProgress(true); 1172 mAmbientState.onBeginDrag(v); 1173 updateContinuousShadowDrawing(); 1174 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) { 1175 mDragAnimPendingChildren.add(v); 1176 mNeedsAnimation = true; 1177 } 1178 requestChildrenUpdate(); 1179 } 1180 1181 public static boolean isPinnedHeadsUp(View v) { 1182 if (v instanceof ExpandableNotificationRow) { 1183 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1184 return row.isHeadsUp() && row.isPinned(); 1185 } 1186 return false; 1187 } 1188 1189 private boolean isHeadsUp(View v) { 1190 if (v instanceof ExpandableNotificationRow) { 1191 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1192 return row.isHeadsUp(); 1193 } 1194 return false; 1195 } 1196 1197 @Override 1198 public void onDragCancelled(View v) { 1199 mFalsingManager.onNotificatonStopDismissing(); 1200 setSwipingInProgress(false); 1201 } 1202 1203 @Override 1204 public float getFalsingThresholdFactor() { 1205 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 1206 } 1207 1208 @Override 1209 public View getChildAtPosition(MotionEvent ev) { 1210 View child = getChildAtPosition(ev.getX(), ev.getY()); 1211 if (child instanceof ExpandableNotificationRow) { 1212 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 1213 ExpandableNotificationRow parent = row.getNotificationParent(); 1214 if (parent != null && parent.areChildrenExpanded() 1215 && (parent.areGutsExposed() 1216 || mMenuExposedView == parent 1217 || (parent.getNotificationChildren().size() == 1 1218 && parent.isClearable()))) { 1219 // In this case the group is expanded and showing the menu for the 1220 // group, further interaction should apply to the group, not any 1221 // child notifications so we use the parent of the child. We also do the same 1222 // if we only have a single child. 1223 child = parent; 1224 } 1225 } 1226 return child; 1227 } 1228 1229 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { 1230 getLocationOnScreen(mTempInt2); 1231 float localTouchY = touchY - mTempInt2[1]; 1232 1233 ExpandableView closestChild = null; 1234 float minDist = Float.MAX_VALUE; 1235 1236 // find the view closest to the location, accounting for GONE views 1237 final int count = getChildCount(); 1238 for (int childIdx = 0; childIdx < count; childIdx++) { 1239 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 1240 if (slidingChild.getVisibility() == GONE 1241 || slidingChild instanceof StackScrollerDecorView) { 1242 continue; 1243 } 1244 float childTop = slidingChild.getTranslationY(); 1245 float top = childTop + slidingChild.getClipTopAmount(); 1246 float bottom = childTop + slidingChild.getActualHeight() 1247 - slidingChild.getClipBottomAmount(); 1248 1249 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY)); 1250 if (dist < minDist) { 1251 closestChild = slidingChild; 1252 minDist = dist; 1253 } 1254 } 1255 return closestChild; 1256 } 1257 1258 @Override 1259 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 1260 getLocationOnScreen(mTempInt2); 1261 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); 1262 } 1263 1264 @Override 1265 public ExpandableView getChildAtPosition(float touchX, float touchY) { 1266 return getChildAtPosition(touchX, touchY, true /* requireMinHeight */); 1267 1268 } 1269 1270 /** 1271 * Get the child at a certain screen location. 1272 * 1273 * @param touchX the x coordinate 1274 * @param touchY the y coordinate 1275 * @param requireMinHeight Whether a minimum height is required for a child to be returned. 1276 * @return the child at the given location. 1277 */ 1278 private ExpandableView getChildAtPosition(float touchX, float touchY, 1279 boolean requireMinHeight) { 1280 // find the view under the pointer, accounting for GONE views 1281 final int count = getChildCount(); 1282 for (int childIdx = 0; childIdx < count; childIdx++) { 1283 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 1284 if (slidingChild.getVisibility() != VISIBLE 1285 || slidingChild instanceof StackScrollerDecorView) { 1286 continue; 1287 } 1288 float childTop = slidingChild.getTranslationY(); 1289 float top = childTop + slidingChild.getClipTopAmount(); 1290 float bottom = childTop + slidingChild.getActualHeight() 1291 - slidingChild.getClipBottomAmount(); 1292 1293 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and 1294 // camera affordance). 1295 int left = 0; 1296 int right = getWidth(); 1297 1298 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight) 1299 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 1300 if (slidingChild instanceof ExpandableNotificationRow) { 1301 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; 1302 if (!mIsExpanded && row.isHeadsUp() && row.isPinned() 1303 && mHeadsUpManager.getTopEntry().row != row 1304 && mGroupManager.getGroupSummary( 1305 mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) 1306 != row) { 1307 continue; 1308 } 1309 return row.getViewAtPosition(touchY - childTop); 1310 } 1311 return slidingChild; 1312 } 1313 } 1314 return null; 1315 } 1316 1317 @Override 1318 public boolean canChildBeExpanded(View v) { 1319 return v instanceof ExpandableNotificationRow 1320 && ((ExpandableNotificationRow) v).isExpandable() 1321 && !((ExpandableNotificationRow) v).areGutsExposed() 1322 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); 1323 } 1324 1325 /* Only ever called as a consequence of an expansion gesture in the shade. */ 1326 @Override 1327 public void setUserExpandedChild(View v, boolean userExpanded) { 1328 if (v instanceof ExpandableNotificationRow) { 1329 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1330 if (userExpanded && onKeyguard()) { 1331 // Due to a race when locking the screen while touching, a notification may be 1332 // expanded even after we went back to keyguard. An example of this happens if 1333 // you click in the empty space while expanding a group. 1334 1335 // We also need to un-user lock it here, since otherwise the content height 1336 // calculated might be wrong. We also can't invert the two calls since 1337 // un-userlocking it will trigger a layout switch in the content view. 1338 row.setUserLocked(false); 1339 updateContentHeight(); 1340 notifyHeightChangeListener(row); 1341 return; 1342 } 1343 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); 1344 row.onExpandedByGesture(userExpanded); 1345 } 1346 } 1347 1348 @Override 1349 public void setExpansionCancelled(View v) { 1350 if (v instanceof ExpandableNotificationRow) { 1351 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); 1352 } 1353 } 1354 1355 @Override 1356 public void setUserLockedChild(View v, boolean userLocked) { 1357 if (v instanceof ExpandableNotificationRow) { 1358 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 1359 } 1360 cancelLongPress(); 1361 requestDisallowInterceptTouchEvent(true); 1362 } 1363 1364 @Override 1365 public void expansionStateChanged(boolean isExpanding) { 1366 mExpandingNotification = isExpanding; 1367 if (!mExpandedInThisMotion) { 1368 mMaxScrollAfterExpand = mOwnScrollY; 1369 mExpandedInThisMotion = true; 1370 } 1371 } 1372 1373 @Override 1374 public int getMaxExpandHeight(ExpandableView view) { 1375 return view.getMaxContentHeight(); 1376 } 1377 1378 public void setScrollingEnabled(boolean enable) { 1379 mScrollingEnabled = enable; 1380 } 1381 1382 public void lockScrollTo(View v) { 1383 if (mForcedScroll == v) { 1384 return; 1385 } 1386 mForcedScroll = v; 1387 scrollTo(v); 1388 } 1389 1390 public boolean scrollTo(View v) { 1391 ExpandableView expandableView = (ExpandableView) v; 1392 int positionInLinearLayout = getPositionInLinearLayout(v); 1393 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1394 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1395 1396 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 1397 // that it is not visible anymore. 1398 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1399 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); 1400 mDontReportNextOverScroll = true; 1401 animateScroll(); 1402 return true; 1403 } 1404 return false; 1405 } 1406 1407 /** 1408 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of 1409 * the IME. 1410 */ 1411 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { 1412 return positionInLinearLayout + v.getIntrinsicHeight() + 1413 getImeInset() - getHeight() + getTopPadding(); 1414 } 1415 1416 @Override 1417 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1418 mBottomInset = insets.getSystemWindowInsetBottom(); 1419 1420 int range = getScrollRange(); 1421 if (mOwnScrollY > range) { 1422 // HACK: We're repeatedly getting staggered insets here while the IME is 1423 // animating away. To work around that we'll wait until things have settled. 1424 removeCallbacks(mReclamp); 1425 postDelayed(mReclamp, 50); 1426 } else if (mForcedScroll != null) { 1427 // The scroll was requested before we got the actual inset - in case we need 1428 // to scroll up some more do so now. 1429 scrollTo(mForcedScroll); 1430 } 1431 return insets; 1432 } 1433 1434 private Runnable mReclamp = new Runnable() { 1435 @Override 1436 public void run() { 1437 int range = getScrollRange(); 1438 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); 1439 mDontReportNextOverScroll = true; 1440 mDontClampNextScroll = true; 1441 animateScroll(); 1442 } 1443 }; 1444 1445 public void setExpandingEnabled(boolean enable) { 1446 mExpandHelper.setEnabled(enable); 1447 } 1448 1449 private boolean isScrollingEnabled() { 1450 return mScrollingEnabled; 1451 } 1452 1453 @Override 1454 public boolean canChildBeDismissed(View v) { 1455 return StackScrollAlgorithm.canChildBeDismissed(v); 1456 } 1457 1458 @Override 1459 public boolean isAntiFalsingNeeded() { 1460 return onKeyguard(); 1461 } 1462 1463 private boolean onKeyguard() { 1464 return mStatusBarState == StatusBarState.KEYGUARD; 1465 } 1466 1467 private void setSwipingInProgress(boolean isSwiped) { 1468 mSwipingInProgress = isSwiped; 1469 if(isSwiped) { 1470 requestDisallowInterceptTouchEvent(true); 1471 } 1472 } 1473 1474 @Override 1475 protected void onConfigurationChanged(Configuration newConfig) { 1476 super.onConfigurationChanged(newConfig); 1477 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); 1478 float densityScale = getResources().getDisplayMetrics().density; 1479 mSwipeHelper.setDensityScale(densityScale); 1480 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 1481 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 1482 initView(getContext()); 1483 } 1484 1485 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { 1486 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, 1487 true /* isDismissAll */); 1488 } 1489 1490 @Override 1491 public void snapViewIfNeeded(ExpandableNotificationRow child) { 1492 boolean animate = mIsExpanded || isPinnedHeadsUp(child); 1493 // If the child is showing the notification menu snap to that 1494 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0; 1495 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft); 1496 } 1497 1498 @Override 1499 public ViewGroup getViewParentForNotification(NotificationData.Entry entry) { 1500 return this; 1501 } 1502 1503 @Override 1504 public boolean onTouchEvent(MotionEvent ev) { 1505 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 1506 || ev.getActionMasked()== MotionEvent.ACTION_UP; 1507 handleEmptySpaceClick(ev); 1508 boolean expandWantsIt = false; 1509 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) { 1510 if (isCancelOrUp) { 1511 mExpandHelper.onlyObserveMovements(false); 1512 } 1513 boolean wasExpandingBefore = mExpandingNotification; 1514 expandWantsIt = mExpandHelper.onTouchEvent(ev); 1515 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore 1516 && !mDisallowScrollingInThisMotion) { 1517 dispatchDownEventToScroller(ev); 1518 } 1519 } 1520 boolean scrollerWantsIt = false; 1521 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification 1522 && !mDisallowScrollingInThisMotion) { 1523 scrollerWantsIt = onScrollTouch(ev); 1524 } 1525 boolean horizontalSwipeWantsIt = false; 1526 if (!mIsBeingDragged 1527 && !mExpandingNotification 1528 && !mExpandedInThisMotion 1529 && !mOnlyScrollingInThisMotion 1530 && !mDisallowDismissInThisMotion) { 1531 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 1532 } 1533 1534 // Check if we need to clear any snooze leavebehinds 1535 NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); 1536 if (guts != null && !isTouchInView(ev, guts) 1537 && guts.getGutsContent() instanceof NotificationSnooze) { 1538 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); 1539 if ((ns.isExpanded() && isCancelOrUp) 1540 || (!horizontalSwipeWantsIt && scrollerWantsIt)) { 1541 // If the leavebehind is expanded we clear it on the next up event, otherwise we 1542 // clear it on the next non-horizontal swipe or expand event. 1543 checkSnoozeLeavebehind(); 1544 } 1545 } 1546 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 1547 mCheckForLeavebehind = true; 1548 } 1549 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); 1550 } 1551 1552 private void dispatchDownEventToScroller(MotionEvent ev) { 1553 MotionEvent downEvent = MotionEvent.obtain(ev); 1554 downEvent.setAction(MotionEvent.ACTION_DOWN); 1555 onScrollTouch(downEvent); 1556 downEvent.recycle(); 1557 } 1558 1559 @Override 1560 public boolean onGenericMotionEvent(MotionEvent event) { 1561 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification 1562 || mDisallowScrollingInThisMotion) { 1563 return false; 1564 } 1565 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 1566 switch (event.getAction()) { 1567 case MotionEvent.ACTION_SCROLL: { 1568 if (!mIsBeingDragged) { 1569 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1570 if (vscroll != 0) { 1571 final int delta = (int) (vscroll * getVerticalScrollFactor()); 1572 final int range = getScrollRange(); 1573 int oldScrollY = mOwnScrollY; 1574 int newScrollY = oldScrollY - delta; 1575 if (newScrollY < 0) { 1576 newScrollY = 0; 1577 } else if (newScrollY > range) { 1578 newScrollY = range; 1579 } 1580 if (newScrollY != oldScrollY) { 1581 setOwnScrollY(newScrollY); 1582 return true; 1583 } 1584 } 1585 } 1586 } 1587 } 1588 } 1589 return super.onGenericMotionEvent(event); 1590 } 1591 1592 private boolean onScrollTouch(MotionEvent ev) { 1593 if (!isScrollingEnabled()) { 1594 return false; 1595 } 1596 if (isInsideQsContainer(ev) && !mIsBeingDragged) { 1597 return false; 1598 } 1599 mForcedScroll = null; 1600 initVelocityTrackerIfNotExists(); 1601 mVelocityTracker.addMovement(ev); 1602 1603 final int action = ev.getAction(); 1604 1605 switch (action & MotionEvent.ACTION_MASK) { 1606 case MotionEvent.ACTION_DOWN: { 1607 if (getChildCount() == 0 || !isInContentBounds(ev)) { 1608 return false; 1609 } 1610 boolean isBeingDragged = !mScroller.isFinished(); 1611 setIsBeingDragged(isBeingDragged); 1612 /* 1613 * If being flinged and user touches, stop the fling. isFinished 1614 * will be false if being flinged. 1615 */ 1616 if (!mScroller.isFinished()) { 1617 mScroller.forceFinished(true); 1618 } 1619 1620 // Remember where the motion event started 1621 mLastMotionY = (int) ev.getY(); 1622 mDownX = (int) ev.getX(); 1623 mActivePointerId = ev.getPointerId(0); 1624 break; 1625 } 1626 case MotionEvent.ACTION_MOVE: 1627 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1628 if (activePointerIndex == -1) { 1629 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 1630 break; 1631 } 1632 1633 final int y = (int) ev.getY(activePointerIndex); 1634 final int x = (int) ev.getX(activePointerIndex); 1635 int deltaY = mLastMotionY - y; 1636 final int xDiff = Math.abs(x - mDownX); 1637 final int yDiff = Math.abs(deltaY); 1638 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { 1639 setIsBeingDragged(true); 1640 if (deltaY > 0) { 1641 deltaY -= mTouchSlop; 1642 } else { 1643 deltaY += mTouchSlop; 1644 } 1645 } 1646 if (mIsBeingDragged) { 1647 // Scroll to follow the motion event 1648 mLastMotionY = y; 1649 int range = getScrollRange(); 1650 if (mExpandedInThisMotion) { 1651 range = Math.min(range, mMaxScrollAfterExpand); 1652 } 1653 1654 float scrollAmount; 1655 if (deltaY < 0) { 1656 scrollAmount = overScrollDown(deltaY); 1657 } else { 1658 scrollAmount = overScrollUp(deltaY, range); 1659 } 1660 1661 // Calling customOverScrollBy will call onCustomOverScrolled, which 1662 // sets the scrolling if applicable. 1663 if (scrollAmount != 0.0f) { 1664 // The scrolling motion could not be compensated with the 1665 // existing overScroll, we have to scroll the view 1666 customOverScrollBy((int) scrollAmount, mOwnScrollY, 1667 range, getHeight() / 2); 1668 // If we're scrolling, leavebehinds should be dismissed 1669 checkSnoozeLeavebehind(); 1670 } 1671 } 1672 break; 1673 case MotionEvent.ACTION_UP: 1674 if (mIsBeingDragged) { 1675 final VelocityTracker velocityTracker = mVelocityTracker; 1676 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1677 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1678 1679 if (shouldOverScrollFling(initialVelocity)) { 1680 onOverScrollFling(true, initialVelocity); 1681 } else { 1682 if (getChildCount() > 0) { 1683 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 1684 float currentOverScrollTop = getCurrentOverScrollAmount(true); 1685 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 1686 fling(-initialVelocity); 1687 } else { 1688 onOverScrollFling(false, initialVelocity); 1689 } 1690 } else { 1691 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 1692 getScrollRange())) { 1693 animateScroll(); 1694 } 1695 } 1696 } 1697 } 1698 mActivePointerId = INVALID_POINTER; 1699 endDrag(); 1700 } 1701 1702 break; 1703 case MotionEvent.ACTION_CANCEL: 1704 if (mIsBeingDragged && getChildCount() > 0) { 1705 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 1706 animateScroll(); 1707 } 1708 mActivePointerId = INVALID_POINTER; 1709 endDrag(); 1710 } 1711 break; 1712 case MotionEvent.ACTION_POINTER_DOWN: { 1713 final int index = ev.getActionIndex(); 1714 mLastMotionY = (int) ev.getY(index); 1715 mDownX = (int) ev.getX(index); 1716 mActivePointerId = ev.getPointerId(index); 1717 break; 1718 } 1719 case MotionEvent.ACTION_POINTER_UP: 1720 onSecondaryPointerUp(ev); 1721 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 1722 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 1723 break; 1724 } 1725 return true; 1726 } 1727 1728 protected boolean isInsideQsContainer(MotionEvent ev) { 1729 return ev.getY() < mQsContainer.getBottom(); 1730 } 1731 1732 private void onOverScrollFling(boolean open, int initialVelocity) { 1733 if (mOverscrollTopChangedListener != null) { 1734 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 1735 } 1736 mDontReportNextOverScroll = true; 1737 setOverScrollAmount(0.0f, true, false); 1738 } 1739 1740 /** 1741 * Perform a scroll upwards and adapt the overscroll amounts accordingly 1742 * 1743 * @param deltaY The amount to scroll upwards, has to be positive. 1744 * @return The amount of scrolling to be performed by the scroller, 1745 * not handled by the overScroll amount. 1746 */ 1747 private float overScrollUp(int deltaY, int range) { 1748 deltaY = Math.max(deltaY, 0); 1749 float currentTopAmount = getCurrentOverScrollAmount(true); 1750 float newTopAmount = currentTopAmount - deltaY; 1751 if (currentTopAmount > 0) { 1752 setOverScrollAmount(newTopAmount, true /* onTop */, 1753 false /* animate */); 1754 } 1755 // Top overScroll might not grab all scrolling motion, 1756 // we have to scroll as well. 1757 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 1758 float newScrollY = mOwnScrollY + scrollAmount; 1759 if (newScrollY > range) { 1760 if (!mExpandedInThisMotion) { 1761 float currentBottomPixels = getCurrentOverScrolledPixels(false); 1762 // We overScroll on the top 1763 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 1764 false /* onTop */, 1765 false /* animate */); 1766 } 1767 setOwnScrollY(range); 1768 scrollAmount = 0.0f; 1769 } 1770 return scrollAmount; 1771 } 1772 1773 /** 1774 * Perform a scroll downward and adapt the overscroll amounts accordingly 1775 * 1776 * @param deltaY The amount to scroll downwards, has to be negative. 1777 * @return The amount of scrolling to be performed by the scroller, 1778 * not handled by the overScroll amount. 1779 */ 1780 private float overScrollDown(int deltaY) { 1781 deltaY = Math.min(deltaY, 0); 1782 float currentBottomAmount = getCurrentOverScrollAmount(false); 1783 float newBottomAmount = currentBottomAmount + deltaY; 1784 if (currentBottomAmount > 0) { 1785 setOverScrollAmount(newBottomAmount, false /* onTop */, 1786 false /* animate */); 1787 } 1788 // Bottom overScroll might not grab all scrolling motion, 1789 // we have to scroll as well. 1790 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 1791 float newScrollY = mOwnScrollY + scrollAmount; 1792 if (newScrollY < 0) { 1793 float currentTopPixels = getCurrentOverScrolledPixels(true); 1794 // We overScroll on the top 1795 setOverScrolledPixels(currentTopPixels - newScrollY, 1796 true /* onTop */, 1797 false /* animate */); 1798 setOwnScrollY(0); 1799 scrollAmount = 0.0f; 1800 } 1801 return scrollAmount; 1802 } 1803 1804 private void onSecondaryPointerUp(MotionEvent ev) { 1805 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 1806 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 1807 final int pointerId = ev.getPointerId(pointerIndex); 1808 if (pointerId == mActivePointerId) { 1809 // This was our active pointer going up. Choose a new 1810 // active pointer and adjust accordingly. 1811 // TODO: Make this decision more intelligent. 1812 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1813 mLastMotionY = (int) ev.getY(newPointerIndex); 1814 mActivePointerId = ev.getPointerId(newPointerIndex); 1815 if (mVelocityTracker != null) { 1816 mVelocityTracker.clear(); 1817 } 1818 } 1819 } 1820 1821 private void initVelocityTrackerIfNotExists() { 1822 if (mVelocityTracker == null) { 1823 mVelocityTracker = VelocityTracker.obtain(); 1824 } 1825 } 1826 1827 private void recycleVelocityTracker() { 1828 if (mVelocityTracker != null) { 1829 mVelocityTracker.recycle(); 1830 mVelocityTracker = null; 1831 } 1832 } 1833 1834 private void initOrResetVelocityTracker() { 1835 if (mVelocityTracker == null) { 1836 mVelocityTracker = VelocityTracker.obtain(); 1837 } else { 1838 mVelocityTracker.clear(); 1839 } 1840 } 1841 1842 public void setFinishScrollingCallback(Runnable runnable) { 1843 mFinishScrollingCallback = runnable; 1844 } 1845 1846 private void animateScroll() { 1847 if (mScroller.computeScrollOffset()) { 1848 int oldY = mOwnScrollY; 1849 int y = mScroller.getCurrY(); 1850 1851 if (oldY != y) { 1852 int range = getScrollRange(); 1853 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 1854 float currVelocity = mScroller.getCurrVelocity(); 1855 if (currVelocity >= mMinimumVelocity) { 1856 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 1857 } 1858 } 1859 1860 if (mDontClampNextScroll) { 1861 range = Math.max(range, oldY); 1862 } 1863 customOverScrollBy(y - oldY, oldY, range, 1864 (int) (mMaxOverScroll)); 1865 } 1866 1867 postOnAnimation(mAnimateScroll); 1868 } else { 1869 mDontClampNextScroll = false; 1870 if (mFinishScrollingCallback != null) { 1871 mFinishScrollingCallback.run(); 1872 } 1873 } 1874 } 1875 1876 private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, 1877 int maxOverScrollY) { 1878 1879 int newScrollY = scrollY + deltaY; 1880 final int top = -maxOverScrollY; 1881 final int bottom = maxOverScrollY + scrollRangeY; 1882 1883 boolean clampedY = false; 1884 if (newScrollY > bottom) { 1885 newScrollY = bottom; 1886 clampedY = true; 1887 } else if (newScrollY < top) { 1888 newScrollY = top; 1889 clampedY = true; 1890 } 1891 1892 onCustomOverScrolled(newScrollY, clampedY); 1893 1894 return clampedY; 1895 } 1896 1897 /** 1898 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 1899 * overscroll effect based on numPixels. By default this will also cancel animations on the 1900 * same overScroll edge. 1901 * 1902 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 1903 * the rubber-banding logic. 1904 * @param onTop Should the effect be applied on top of the scroller. 1905 * @param animate Should an animation be performed. 1906 */ 1907 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 1908 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 1909 } 1910 1911 /** 1912 * Set the effective overScroll amount which will be directly reflected in the layout. 1913 * By default this will also cancel animations on the same overScroll edge. 1914 * 1915 * @param amount The amount to overScroll by. 1916 * @param onTop Should the effect be applied on top of the scroller. 1917 * @param animate Should an animation be performed. 1918 */ 1919 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 1920 setOverScrollAmount(amount, onTop, animate, true); 1921 } 1922 1923 /** 1924 * Set the effective overScroll amount which will be directly reflected in the layout. 1925 * 1926 * @param amount The amount to overScroll by. 1927 * @param onTop Should the effect be applied on top of the scroller. 1928 * @param animate Should an animation be performed. 1929 * @param cancelAnimators Should running animations be cancelled. 1930 */ 1931 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1932 boolean cancelAnimators) { 1933 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 1934 } 1935 1936 /** 1937 * Set the effective overScroll amount which will be directly reflected in the layout. 1938 * 1939 * @param amount The amount to overScroll by. 1940 * @param onTop Should the effect be applied on top of the scroller. 1941 * @param animate Should an animation be performed. 1942 * @param cancelAnimators Should running animations be cancelled. 1943 * @param isRubberbanded The value which will be passed to 1944 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 1945 */ 1946 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1947 boolean cancelAnimators, boolean isRubberbanded) { 1948 if (cancelAnimators) { 1949 mStateAnimator.cancelOverScrollAnimators(onTop); 1950 } 1951 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 1952 } 1953 1954 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 1955 boolean isRubberbanded) { 1956 amount = Math.max(0, amount); 1957 if (animate) { 1958 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 1959 } else { 1960 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 1961 mAmbientState.setOverScrollAmount(amount, onTop); 1962 if (onTop) { 1963 notifyOverscrollTopListener(amount, isRubberbanded); 1964 } 1965 requestChildrenUpdate(); 1966 } 1967 } 1968 1969 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 1970 mExpandHelper.onlyObserveMovements(amount > 1.0f); 1971 if (mDontReportNextOverScroll) { 1972 mDontReportNextOverScroll = false; 1973 return; 1974 } 1975 if (mOverscrollTopChangedListener != null) { 1976 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 1977 } 1978 } 1979 1980 public void setOverscrollTopChangedListener( 1981 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1982 mOverscrollTopChangedListener = overscrollTopChangedListener; 1983 } 1984 1985 public float getCurrentOverScrollAmount(boolean top) { 1986 return mAmbientState.getOverScrollAmount(top); 1987 } 1988 1989 public float getCurrentOverScrolledPixels(boolean top) { 1990 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1991 } 1992 1993 private void setOverScrolledPixels(float amount, boolean onTop) { 1994 if (onTop) { 1995 mOverScrolledTopPixels = amount; 1996 } else { 1997 mOverScrolledBottomPixels = amount; 1998 } 1999 } 2000 2001 private void onCustomOverScrolled(int scrollY, boolean clampedY) { 2002 // Treat animating scrolls differently; see #computeScroll() for why. 2003 if (!mScroller.isFinished()) { 2004 setOwnScrollY(scrollY); 2005 if (clampedY) { 2006 springBack(); 2007 } else { 2008 float overScrollTop = getCurrentOverScrollAmount(true); 2009 if (mOwnScrollY < 0) { 2010 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 2011 } else { 2012 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 2013 } 2014 } 2015 } else { 2016 setOwnScrollY(scrollY); 2017 } 2018 } 2019 2020 private void springBack() { 2021 int scrollRange = getScrollRange(); 2022 boolean overScrolledTop = mOwnScrollY <= 0; 2023 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 2024 if (overScrolledTop || overScrolledBottom) { 2025 boolean onTop; 2026 float newAmount; 2027 if (overScrolledTop) { 2028 onTop = true; 2029 newAmount = -mOwnScrollY; 2030 setOwnScrollY(0); 2031 mDontReportNextOverScroll = true; 2032 } else { 2033 onTop = false; 2034 newAmount = mOwnScrollY - scrollRange; 2035 setOwnScrollY(scrollRange); 2036 } 2037 setOverScrollAmount(newAmount, onTop, false); 2038 setOverScrollAmount(0.0f, onTop, true); 2039 mScroller.forceFinished(true); 2040 } 2041 } 2042 2043 private int getScrollRange() { 2044 int contentHeight = getContentHeight(); 2045 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight); 2046 int imeInset = getImeInset(); 2047 scrollRange += Math.min(imeInset, Math.max(0, 2048 getContentHeight() - (getHeight() - imeInset))); 2049 return scrollRange; 2050 } 2051 2052 private int getImeInset() { 2053 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight())); 2054 } 2055 2056 /** 2057 * @return the first child which has visibility unequal to GONE 2058 */ 2059 public ExpandableView getFirstChildNotGone() { 2060 int childCount = getChildCount(); 2061 for (int i = 0; i < childCount; i++) { 2062 View child = getChildAt(i); 2063 if (child.getVisibility() != View.GONE && child != mShelf) { 2064 return (ExpandableView) child; 2065 } 2066 } 2067 return null; 2068 } 2069 2070 /** 2071 * @return the child before the given view which has visibility unequal to GONE 2072 */ 2073 public ExpandableView getViewBeforeView(ExpandableView view) { 2074 ExpandableView previousView = null; 2075 int childCount = getChildCount(); 2076 for (int i = 0; i < childCount; i++) { 2077 View child = getChildAt(i); 2078 if (child == view) { 2079 return previousView; 2080 } 2081 if (child.getVisibility() != View.GONE) { 2082 previousView = (ExpandableView) child; 2083 } 2084 } 2085 return null; 2086 } 2087 2088 /** 2089 * @return The first child which has visibility unequal to GONE which is currently below the 2090 * given translationY or equal to it. 2091 */ 2092 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) { 2093 int childCount = getChildCount(); 2094 for (int i = 0; i < childCount; i++) { 2095 View child = getChildAt(i); 2096 if (child.getVisibility() == View.GONE) { 2097 continue; 2098 } 2099 float rowTranslation = child.getTranslationY(); 2100 if (rowTranslation >= translationY) { 2101 return child; 2102 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) { 2103 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2104 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { 2105 List<ExpandableNotificationRow> notificationChildren = 2106 row.getNotificationChildren(); 2107 for (int childIndex = 0; childIndex < notificationChildren.size(); 2108 childIndex++) { 2109 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex); 2110 if (rowChild.getTranslationY() + rowTranslation >= translationY) { 2111 return rowChild; 2112 } 2113 } 2114 } 2115 } 2116 } 2117 return null; 2118 } 2119 2120 /** 2121 * @return the last child which has visibility unequal to GONE 2122 */ 2123 public View getLastChildNotGone() { 2124 int childCount = getChildCount(); 2125 for (int i = childCount - 1; i >= 0; i--) { 2126 View child = getChildAt(i); 2127 if (child.getVisibility() != View.GONE && child != mShelf) { 2128 return child; 2129 } 2130 } 2131 return null; 2132 } 2133 2134 /** 2135 * @return the number of children which have visibility unequal to GONE 2136 */ 2137 public int getNotGoneChildCount() { 2138 int childCount = getChildCount(); 2139 int count = 0; 2140 for (int i = 0; i < childCount; i++) { 2141 ExpandableView child = (ExpandableView) getChildAt(i); 2142 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) { 2143 count++; 2144 } 2145 } 2146 return count; 2147 } 2148 2149 public int getContentHeight() { 2150 return mContentHeight; 2151 } 2152 2153 private void updateContentHeight() { 2154 int height = 0; 2155 float previousPaddingRequest = mPaddingBetweenElements; 2156 float previousPaddingAmount = 0.0f; 2157 int numShownItems = 0; 2158 boolean finish = false; 2159 int maxDisplayedNotifications = mAmbientState.isFullyDark() 2160 ? (hasPulsingNotifications() ? 1 : 0) 2161 : mMaxDisplayedNotifications; 2162 2163 for (int i = 0; i < getChildCount(); i++) { 2164 ExpandableView expandableView = (ExpandableView) getChildAt(i); 2165 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard(); 2166 if (expandableView.getVisibility() != View.GONE 2167 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) { 2168 boolean limitReached = maxDisplayedNotifications != -1 2169 && numShownItems >= maxDisplayedNotifications; 2170 boolean notificationOnAmbientThatIsNotPulsing = mAmbientState.isFullyDark() 2171 && hasPulsingNotifications() 2172 && expandableView instanceof ExpandableNotificationRow 2173 && !isPulsing(((ExpandableNotificationRow) expandableView).getEntry()); 2174 if (limitReached || notificationOnAmbientThatIsNotPulsing) { 2175 expandableView = mShelf; 2176 finish = true; 2177 } 2178 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount(); 2179 float padding; 2180 if (increasedPaddingAmount >= 0.0f) { 2181 padding = (int) NotificationUtils.interpolate( 2182 previousPaddingRequest, 2183 mIncreasedPaddingBetweenElements, 2184 increasedPaddingAmount); 2185 previousPaddingRequest = (int) NotificationUtils.interpolate( 2186 mPaddingBetweenElements, 2187 mIncreasedPaddingBetweenElements, 2188 increasedPaddingAmount); 2189 } else { 2190 int ownPadding = (int) NotificationUtils.interpolate( 2191 0, 2192 mPaddingBetweenElements, 2193 1.0f + increasedPaddingAmount); 2194 if (previousPaddingAmount > 0.0f) { 2195 padding = (int) NotificationUtils.interpolate( 2196 ownPadding, 2197 mIncreasedPaddingBetweenElements, 2198 previousPaddingAmount); 2199 } else { 2200 padding = ownPadding; 2201 } 2202 previousPaddingRequest = ownPadding; 2203 } 2204 if (height != 0) { 2205 height += padding; 2206 } 2207 previousPaddingAmount = increasedPaddingAmount; 2208 height += expandableView.getIntrinsicHeight(); 2209 numShownItems++; 2210 if (finish) { 2211 break; 2212 } 2213 } 2214 } 2215 mIntrinsicContentHeight = height; 2216 mContentHeight = height + mTopPadding + mBottomMargin; 2217 updateScrollability(); 2218 clampScrollPosition(); 2219 mAmbientState.setLayoutMaxHeight(mContentHeight); 2220 } 2221 2222 private boolean isPulsing(NotificationData.Entry entry) { 2223 return mAmbientState.isPulsing(entry); 2224 } 2225 2226 @Override 2227 public boolean hasPulsingNotifications() { 2228 return mPulsing; 2229 } 2230 2231 private void updateScrollability() { 2232 boolean scrollable = !mQsExpanded && getScrollRange() > 0; 2233 if (scrollable != mScrollable) { 2234 mScrollable = scrollable; 2235 setFocusable(scrollable); 2236 updateForwardAndBackwardScrollability(); 2237 } 2238 } 2239 2240 private void updateForwardAndBackwardScrollability() { 2241 boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange(); 2242 boolean backwardsScrollable = mScrollable && mOwnScrollY > 0; 2243 boolean changed = forwardScrollable != mForwardScrollable 2244 || backwardsScrollable != mBackwardScrollable; 2245 mForwardScrollable = forwardScrollable; 2246 mBackwardScrollable = backwardsScrollable; 2247 if (changed) { 2248 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 2249 } 2250 } 2251 2252 private void updateBackground() { 2253 // No need to update the background color if it's not being drawn. 2254 if (!mShouldDrawNotificationBackground || mAmbientState.isFullyDark()) { 2255 return; 2256 } 2257 2258 updateBackgroundBounds(); 2259 if (!mCurrentBounds.equals(mBackgroundBounds)) { 2260 boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom 2261 || areBoundsAnimating(); 2262 if (!isExpanded()) { 2263 abortBackgroundAnimators(); 2264 animate = false; 2265 } 2266 if (animate) { 2267 startBackgroundAnimation(); 2268 } else { 2269 mCurrentBounds.set(mBackgroundBounds); 2270 applyCurrentBackgroundBounds(); 2271 } 2272 } else { 2273 abortBackgroundAnimators(); 2274 } 2275 mAnimateNextBackgroundBottom = false; 2276 mAnimateNextBackgroundTop = false; 2277 } 2278 2279 private void abortBackgroundAnimators() { 2280 if (mBottomAnimator != null) { 2281 mBottomAnimator.cancel(); 2282 } 2283 if (mTopAnimator != null) { 2284 mTopAnimator.cancel(); 2285 } 2286 } 2287 2288 private boolean areBoundsAnimating() { 2289 return mBottomAnimator != null || mTopAnimator != null; 2290 } 2291 2292 private void startBackgroundAnimation() { 2293 // left and right are always instantly applied 2294 mCurrentBounds.left = mBackgroundBounds.left; 2295 mCurrentBounds.right = mBackgroundBounds.right; 2296 startBottomAnimation(); 2297 startTopAnimation(); 2298 } 2299 2300 private void startTopAnimation() { 2301 int previousEndValue = mEndAnimationRect.top; 2302 int newEndValue = mBackgroundBounds.top; 2303 ObjectAnimator previousAnimator = mTopAnimator; 2304 if (previousAnimator != null && previousEndValue == newEndValue) { 2305 return; 2306 } 2307 if (!mAnimateNextBackgroundTop) { 2308 // just a local update was performed 2309 if (previousAnimator != null) { 2310 // we need to increase all animation keyframes of the previous animator by the 2311 // relative change to the end value 2312 int previousStartValue = mStartAnimationRect.top; 2313 PropertyValuesHolder[] values = previousAnimator.getValues(); 2314 values[0].setIntValues(previousStartValue, newEndValue); 2315 mStartAnimationRect.top = previousStartValue; 2316 mEndAnimationRect.top = newEndValue; 2317 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 2318 return; 2319 } else { 2320 // no new animation needed, let's just apply the value 2321 setBackgroundTop(newEndValue); 2322 return; 2323 } 2324 } 2325 if (previousAnimator != null) { 2326 previousAnimator.cancel(); 2327 } 2328 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop", 2329 mCurrentBounds.top, newEndValue); 2330 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; 2331 animator.setInterpolator(interpolator); 2332 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 2333 // remove the tag when the animation is finished 2334 animator.addListener(new AnimatorListenerAdapter() { 2335 @Override 2336 public void onAnimationEnd(Animator animation) { 2337 mStartAnimationRect.top = -1; 2338 mEndAnimationRect.top = -1; 2339 mTopAnimator = null; 2340 } 2341 }); 2342 animator.start(); 2343 mStartAnimationRect.top = mCurrentBounds.top; 2344 mEndAnimationRect.top = newEndValue; 2345 mTopAnimator = animator; 2346 } 2347 2348 private void startBottomAnimation() { 2349 int previousStartValue = mStartAnimationRect.bottom; 2350 int previousEndValue = mEndAnimationRect.bottom; 2351 int newEndValue = mBackgroundBounds.bottom; 2352 ObjectAnimator previousAnimator = mBottomAnimator; 2353 if (previousAnimator != null && previousEndValue == newEndValue) { 2354 return; 2355 } 2356 if (!mAnimateNextBackgroundBottom) { 2357 // just a local update was performed 2358 if (previousAnimator != null) { 2359 // we need to increase all animation keyframes of the previous animator by the 2360 // relative change to the end value 2361 PropertyValuesHolder[] values = previousAnimator.getValues(); 2362 values[0].setIntValues(previousStartValue, newEndValue); 2363 mStartAnimationRect.bottom = previousStartValue; 2364 mEndAnimationRect.bottom = newEndValue; 2365 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 2366 return; 2367 } else { 2368 // no new animation needed, let's just apply the value 2369 setBackgroundBottom(newEndValue); 2370 return; 2371 } 2372 } 2373 if (previousAnimator != null) { 2374 previousAnimator.cancel(); 2375 } 2376 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom", 2377 mCurrentBounds.bottom, newEndValue); 2378 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; 2379 animator.setInterpolator(interpolator); 2380 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 2381 // remove the tag when the animation is finished 2382 animator.addListener(new AnimatorListenerAdapter() { 2383 @Override 2384 public void onAnimationEnd(Animator animation) { 2385 mStartAnimationRect.bottom = -1; 2386 mEndAnimationRect.bottom = -1; 2387 mBottomAnimator = null; 2388 } 2389 }); 2390 animator.start(); 2391 mStartAnimationRect.bottom = mCurrentBounds.bottom; 2392 mEndAnimationRect.bottom = newEndValue; 2393 mBottomAnimator = animator; 2394 } 2395 2396 private void setBackgroundTop(int top) { 2397 mCurrentBounds.top = top; 2398 applyCurrentBackgroundBounds(); 2399 } 2400 2401 public void setBackgroundBottom(int bottom) { 2402 mCurrentBounds.bottom = bottom; 2403 applyCurrentBackgroundBounds(); 2404 } 2405 2406 private void applyCurrentBackgroundBounds() { 2407 // If the background of the notification is not being drawn, then there is no need to 2408 // exclude an area in the scrim. Rather, the scrim's color should serve as the background. 2409 if (!mShouldDrawNotificationBackground) { 2410 return; 2411 } 2412 2413 final boolean awake = mDarkAmount != 0 || mAmbientState.isDark(); 2414 mScrimController.setExcludedBackgroundArea( 2415 mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null 2416 : mCurrentBounds); 2417 invalidate(); 2418 } 2419 2420 /** 2421 * Update the background bounds to the new desired bounds 2422 */ 2423 private void updateBackgroundBounds() { 2424 getLocationInWindow(mTempInt2); 2425 mBackgroundBounds.left = mTempInt2[0] + mSidePaddings; 2426 mBackgroundBounds.right = mTempInt2[0] + getWidth() - mSidePaddings; 2427 2428 if (!mIsExpanded) { 2429 mBackgroundBounds.top = 0; 2430 mBackgroundBounds.bottom = 0; 2431 return; 2432 } 2433 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild; 2434 int top = 0; 2435 if (firstView != null) { 2436 // Round Y up to avoid seeing the background during animation 2437 int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); 2438 if (mAnimateNextBackgroundTop 2439 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY 2440 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) { 2441 // we're ending up at the same location as we are now, lets just skip the animation 2442 top = finalTranslationY; 2443 } else { 2444 top = (int) Math.ceil(firstView.getTranslationY()); 2445 } 2446 } 2447 ActivatableNotificationView lastView = 2448 mShelf.hasItemsInStableShelf() && mShelf.getVisibility() != GONE 2449 ? mShelf 2450 : mLastVisibleBackgroundChild; 2451 int bottom; 2452 if (lastView != null) { 2453 int finalTranslationY; 2454 if (lastView == mShelf) { 2455 finalTranslationY = (int) mShelf.getTranslationY(); 2456 } else { 2457 finalTranslationY = (int) ViewState.getFinalTranslationY(lastView); 2458 } 2459 int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); 2460 int finalBottom = finalTranslationY + finalHeight - lastView.getClipBottomAmount(); 2461 if (mAnimateNextBackgroundBottom 2462 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom 2463 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) { 2464 // we're ending up at the same location as we are now, lets just skip the animation 2465 bottom = finalBottom; 2466 } else { 2467 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() 2468 - lastView.getClipBottomAmount()); 2469 } 2470 } else { 2471 top = mTopPadding; 2472 bottom = top; 2473 } 2474 if (mStatusBarState != StatusBarState.KEYGUARD) { 2475 top = (int) Math.max(mTopPadding + mStackTranslation, top); 2476 } else { 2477 // otherwise the animation from the shade to the keyguard will jump as it's maxed 2478 top = Math.max(0, top); 2479 } 2480 mBackgroundBounds.top = top; 2481 mBackgroundBounds.bottom = Math.max(bottom, top); 2482 } 2483 2484 private ActivatableNotificationView getFirstPinnedHeadsUp() { 2485 int childCount = getChildCount(); 2486 for (int i = 0; i < childCount; i++) { 2487 View child = getChildAt(i); 2488 if (child.getVisibility() != View.GONE 2489 && child instanceof ExpandableNotificationRow) { 2490 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2491 if (row.isPinned()) { 2492 return row; 2493 } 2494 } 2495 } 2496 return null; 2497 } 2498 2499 private ActivatableNotificationView getLastChildWithBackground() { 2500 int childCount = getChildCount(); 2501 for (int i = childCount - 1; i >= 0; i--) { 2502 View child = getChildAt(i); 2503 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView 2504 && child != mShelf) { 2505 return (ActivatableNotificationView) child; 2506 } 2507 } 2508 return null; 2509 } 2510 2511 private ActivatableNotificationView getFirstChildWithBackground() { 2512 int childCount = getChildCount(); 2513 for (int i = 0; i < childCount; i++) { 2514 View child = getChildAt(i); 2515 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView 2516 && child != mShelf) { 2517 return (ActivatableNotificationView) child; 2518 } 2519 } 2520 return null; 2521 } 2522 2523 /** 2524 * Fling the scroll view 2525 * 2526 * @param velocityY The initial velocity in the Y direction. Positive 2527 * numbers mean that the finger/cursor is moving down the screen, 2528 * which means we want to scroll towards the top. 2529 */ 2530 protected void fling(int velocityY) { 2531 if (getChildCount() > 0) { 2532 int scrollRange = getScrollRange(); 2533 2534 float topAmount = getCurrentOverScrollAmount(true); 2535 float bottomAmount = getCurrentOverScrollAmount(false); 2536 if (velocityY < 0 && topAmount > 0) { 2537 setOwnScrollY(mOwnScrollY - (int) topAmount); 2538 mDontReportNextOverScroll = true; 2539 setOverScrollAmount(0, true, false); 2540 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 2541 * mOverflingDistance + topAmount; 2542 } else if (velocityY > 0 && bottomAmount > 0) { 2543 setOwnScrollY((int) (mOwnScrollY + bottomAmount)); 2544 setOverScrollAmount(0, false, false); 2545 mMaxOverScroll = Math.abs(velocityY) / 1000f 2546 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 2547 + bottomAmount; 2548 } else { 2549 // it will be set once we reach the boundary 2550 mMaxOverScroll = 0.0f; 2551 } 2552 int minScrollY = Math.max(0, scrollRange); 2553 if (mExpandedInThisMotion) { 2554 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); 2555 } 2556 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0, 2557 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); 2558 2559 animateScroll(); 2560 } 2561 } 2562 2563 /** 2564 * @return Whether a fling performed on the top overscroll edge lead to the expanded 2565 * overScroll view (i.e QS). 2566 */ 2567 private boolean shouldOverScrollFling(int initialVelocity) { 2568 float topOverScroll = getCurrentOverScrollAmount(true); 2569 return mScrolledToTopOnFirstDown 2570 && !mExpandedInThisMotion 2571 && topOverScroll > mMinTopOverScrollToEscape 2572 && initialVelocity > 0; 2573 } 2574 2575 /** 2576 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into 2577 * account. 2578 * 2579 * @param qsHeight the top padding imposed by the quick settings panel 2580 * @param animate whether to animate the change 2581 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and 2582 * {@code qsHeight} is the final top padding 2583 */ 2584 public void updateTopPadding(float qsHeight, boolean animate, 2585 boolean ignoreIntrinsicPadding) { 2586 int topPadding = (int) qsHeight; 2587 int minStackHeight = getLayoutMinHeight(); 2588 if (topPadding + minStackHeight > getHeight()) { 2589 mTopPaddingOverflow = topPadding + minStackHeight - getHeight(); 2590 } else { 2591 mTopPaddingOverflow = 0; 2592 } 2593 setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding), 2594 animate); 2595 setExpandedHeight(mExpandedHeight); 2596 } 2597 2598 public void setMaxTopPadding(int maxTopPadding) { 2599 mMaxTopPadding = maxTopPadding; 2600 } 2601 2602 public int getLayoutMinHeight() { 2603 if (isHeadsUpTransition()) { 2604 return getTopHeadsUpPinnedHeight(); 2605 } 2606 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); 2607 } 2608 2609 public int getFirstChildIntrinsicHeight() { 2610 final ExpandableView firstChild = getFirstChildNotGone(); 2611 int firstChildMinHeight = firstChild != null 2612 ? firstChild.getIntrinsicHeight() 2613 : mEmptyShadeView != null 2614 ? mEmptyShadeView.getIntrinsicHeight() 2615 : mCollapsedSize; 2616 if (mOwnScrollY > 0) { 2617 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize); 2618 } 2619 return firstChildMinHeight; 2620 } 2621 2622 public float getTopPaddingOverflow() { 2623 return mTopPaddingOverflow; 2624 } 2625 2626 public int getPeekHeight() { 2627 final ExpandableView firstChild = getFirstChildNotGone(); 2628 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight() 2629 : mCollapsedSize; 2630 int shelfHeight = 0; 2631 if (mLastVisibleBackgroundChild != null && mShelf.getVisibility() != GONE) { 2632 shelfHeight = mShelf.getIntrinsicHeight(); 2633 } 2634 return mIntrinsicPadding + firstChildMinHeight + shelfHeight; 2635 } 2636 2637 private int clampPadding(int desiredPadding) { 2638 return Math.max(desiredPadding, mIntrinsicPadding); 2639 } 2640 2641 private float getRubberBandFactor(boolean onTop) { 2642 if (!onTop) { 2643 return RUBBER_BAND_FACTOR_NORMAL; 2644 } 2645 if (mExpandedInThisMotion) { 2646 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 2647 } else if (mIsExpansionChanging || mPanelTracking) { 2648 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 2649 } else if (mScrolledToTopOnFirstDown) { 2650 return 1.0f; 2651 } 2652 return RUBBER_BAND_FACTOR_NORMAL; 2653 } 2654 2655 /** 2656 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 2657 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 2658 * overscroll view (e.g. expand QS). 2659 */ 2660 private boolean isRubberbanded(boolean onTop) { 2661 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking 2662 || !mScrolledToTopOnFirstDown; 2663 } 2664 2665 private void endDrag() { 2666 setIsBeingDragged(false); 2667 2668 recycleVelocityTracker(); 2669 2670 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 2671 setOverScrollAmount(0, true /* onTop */, true /* animate */); 2672 } 2673 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 2674 setOverScrollAmount(0, false /* onTop */, true /* animate */); 2675 } 2676 } 2677 2678 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { 2679 ev.offsetLocation(sourceView.getX(), sourceView.getY()); 2680 ev.offsetLocation(-targetView.getX(), -targetView.getY()); 2681 } 2682 2683 @Override 2684 public boolean onInterceptTouchEvent(MotionEvent ev) { 2685 initDownStates(ev); 2686 handleEmptySpaceClick(ev); 2687 boolean expandWantsIt = false; 2688 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) { 2689 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); 2690 } 2691 boolean scrollWantsIt = false; 2692 if (!mSwipingInProgress && !mExpandingNotification) { 2693 scrollWantsIt = onInterceptTouchEventScroll(ev); 2694 } 2695 boolean swipeWantsIt = false; 2696 if (!mIsBeingDragged 2697 && !mExpandingNotification 2698 && !mExpandedInThisMotion 2699 && !mOnlyScrollingInThisMotion 2700 && !mDisallowDismissInThisMotion) { 2701 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 2702 } 2703 // Check if we need to clear any snooze leavebehinds 2704 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 2705 NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); 2706 if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt 2707 && !scrollWantsIt) { 2708 mCheckForLeavebehind = false; 2709 mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, 2710 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 2711 false /* resetMenu */); 2712 } 2713 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 2714 mCheckForLeavebehind = true; 2715 } 2716 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); 2717 } 2718 2719 private void handleEmptySpaceClick(MotionEvent ev) { 2720 switch (ev.getActionMasked()) { 2721 case MotionEvent.ACTION_MOVE: 2722 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop 2723 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) { 2724 mTouchIsClick = false; 2725 } 2726 break; 2727 case MotionEvent.ACTION_UP: 2728 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && 2729 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { 2730 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); 2731 } 2732 break; 2733 } 2734 } 2735 2736 private void initDownStates(MotionEvent ev) { 2737 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 2738 mExpandedInThisMotion = false; 2739 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 2740 mDisallowScrollingInThisMotion = false; 2741 mDisallowDismissInThisMotion = false; 2742 mTouchIsClick = true; 2743 mInitialTouchX = ev.getX(); 2744 mInitialTouchY = ev.getY(); 2745 } 2746 } 2747 2748 public void setChildTransferInProgress(boolean childTransferInProgress) { 2749 mChildTransferInProgress = childTransferInProgress; 2750 } 2751 2752 @Override 2753 public void onViewRemoved(View child) { 2754 super.onViewRemoved(child); 2755 // we only call our internal methods if this is actually a removal and not just a 2756 // notification which becomes a child notification 2757 if (!mChildTransferInProgress) { 2758 onViewRemovedInternal(child, this); 2759 } 2760 } 2761 2762 @Override 2763 public void cleanUpViewState(View child) { 2764 if (child == mTranslatingParentView) { 2765 mTranslatingParentView = null; 2766 } 2767 mCurrentStackScrollState.removeViewStateForView(child); 2768 } 2769 2770 @Override 2771 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 2772 super.requestDisallowInterceptTouchEvent(disallowIntercept); 2773 if (disallowIntercept) { 2774 cancelLongPress(); 2775 } 2776 } 2777 2778 private void onViewRemovedInternal(View child, ViewGroup container) { 2779 if (mChangePositionInProgress) { 2780 // This is only a position change, don't do anything special 2781 return; 2782 } 2783 ExpandableView expandableView = (ExpandableView) child; 2784 expandableView.setOnHeightChangedListener(null); 2785 mCurrentStackScrollState.removeViewStateForView(child); 2786 updateScrollStateForRemovedChild(expandableView); 2787 boolean animationGenerated = generateRemoveAnimation(child); 2788 if (animationGenerated) { 2789 if (!mSwipedOutViews.contains(child) 2790 || Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) { 2791 container.addTransientView(child, 0); 2792 expandableView.setTransientContainer(container); 2793 } 2794 } else { 2795 mSwipedOutViews.remove(child); 2796 } 2797 updateAnimationState(false, child); 2798 2799 focusNextViewIfFocused(child); 2800 } 2801 2802 private void focusNextViewIfFocused(View view) { 2803 if (view instanceof ExpandableNotificationRow) { 2804 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2805 if (row.shouldRefocusOnDismiss()) { 2806 View nextView = row.getChildAfterViewWhenDismissed(); 2807 if (nextView == null) { 2808 View groupParentWhenDismissed = row.getGroupParentWhenDismissed(); 2809 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null 2810 ? groupParentWhenDismissed.getTranslationY() 2811 : view.getTranslationY(), true /* ignoreChildren */); 2812 } 2813 if (nextView != null) { 2814 nextView.requestAccessibilityFocus(); 2815 } 2816 } 2817 } 2818 2819 } 2820 2821 private boolean isChildInGroup(View child) { 2822 return child instanceof ExpandableNotificationRow 2823 && mGroupManager.isChildInGroupWithSummary( 2824 ((ExpandableNotificationRow) child).getStatusBarNotification()); 2825 } 2826 2827 /** 2828 * Generate a remove animation for a child view. 2829 * 2830 * @param child The view to generate the remove animation for. 2831 * @return Whether an animation was generated. 2832 */ 2833 private boolean generateRemoveAnimation(View child) { 2834 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { 2835 mAddedHeadsUpChildren.remove(child); 2836 return false; 2837 } 2838 if (isClickedHeadsUp(child)) { 2839 // An animation is already running, add it transiently 2840 mClearTransientViewsWhenFinished.add((ExpandableView) child); 2841 return true; 2842 } 2843 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { 2844 if (!mChildrenToAddAnimated.contains(child)) { 2845 // Generate Animations 2846 mChildrenToRemoveAnimated.add(child); 2847 mNeedsAnimation = true; 2848 return true; 2849 } else { 2850 mChildrenToAddAnimated.remove(child); 2851 mFromMoreCardAdditions.remove(child); 2852 return false; 2853 } 2854 } 2855 return false; 2856 } 2857 2858 private boolean isClickedHeadsUp(View child) { 2859 return HeadsUpUtil.isClickedHeadsUpNotification(child); 2860 } 2861 2862 /** 2863 * Remove a removed child view from the heads up animations if it was just added there 2864 * 2865 * @return whether any child was removed from the list to animate 2866 */ 2867 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) { 2868 boolean hasAddEvent = false; 2869 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 2870 ExpandableNotificationRow row = eventPair.first; 2871 boolean isHeadsUp = eventPair.second; 2872 if (child == row) { 2873 mTmpList.add(eventPair); 2874 hasAddEvent |= isHeadsUp; 2875 } 2876 } 2877 if (hasAddEvent) { 2878 // This child was just added lets remove all events. 2879 mHeadsUpChangeAnimations.removeAll(mTmpList); 2880 ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false); 2881 } 2882 mTmpList.clear(); 2883 return hasAddEvent; 2884 } 2885 2886 /** 2887 * @param child the child to query 2888 * @return whether a view is not a top level child but a child notification and that group is 2889 * not expanded 2890 */ 2891 private boolean isChildInInvisibleGroup(View child) { 2892 if (child instanceof ExpandableNotificationRow) { 2893 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2894 ExpandableNotificationRow groupSummary = 2895 mGroupManager.getGroupSummary(row.getStatusBarNotification()); 2896 if (groupSummary != null && groupSummary != row) { 2897 return row.getVisibility() == View.INVISIBLE; 2898 } 2899 } 2900 return false; 2901 } 2902 2903 /** 2904 * Updates the scroll position when a child was removed 2905 * 2906 * @param removedChild the removed child 2907 */ 2908 private void updateScrollStateForRemovedChild(ExpandableView removedChild) { 2909 int startingPosition = getPositionInLinearLayout(removedChild); 2910 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount(); 2911 int padding; 2912 if (increasedPaddingAmount >= 0) { 2913 padding = (int) NotificationUtils.interpolate( 2914 mPaddingBetweenElements, 2915 mIncreasedPaddingBetweenElements, 2916 increasedPaddingAmount); 2917 } else { 2918 padding = (int) NotificationUtils.interpolate( 2919 0, 2920 mPaddingBetweenElements, 2921 1.0f + increasedPaddingAmount); 2922 } 2923 int childHeight = getIntrinsicHeight(removedChild) + padding; 2924 int endPosition = startingPosition + childHeight; 2925 if (endPosition <= mOwnScrollY) { 2926 // This child is fully scrolled of the top, so we have to deduct its height from the 2927 // scrollPosition 2928 setOwnScrollY(mOwnScrollY - childHeight); 2929 } else if (startingPosition < mOwnScrollY) { 2930 // This child is currently being scrolled into, set the scroll position to the start of 2931 // this child 2932 setOwnScrollY(startingPosition); 2933 } 2934 } 2935 2936 private int getIntrinsicHeight(View view) { 2937 if (view instanceof ExpandableView) { 2938 ExpandableView expandableView = (ExpandableView) view; 2939 return expandableView.getIntrinsicHeight(); 2940 } 2941 return view.getHeight(); 2942 } 2943 2944 public int getPositionInLinearLayout(View requestedView) { 2945 ExpandableNotificationRow childInGroup = null; 2946 ExpandableNotificationRow requestedRow = null; 2947 if (isChildInGroup(requestedView)) { 2948 // We're asking for a child in a group. Calculate the position of the parent first, 2949 // then within the parent. 2950 childInGroup = (ExpandableNotificationRow) requestedView; 2951 requestedView = requestedRow = childInGroup.getNotificationParent(); 2952 } 2953 int position = 0; 2954 float previousPaddingRequest = mPaddingBetweenElements; 2955 float previousPaddingAmount = 0.0f; 2956 for (int i = 0; i < getChildCount(); i++) { 2957 ExpandableView child = (ExpandableView) getChildAt(i); 2958 boolean notGone = child.getVisibility() != View.GONE; 2959 if (notGone && !child.hasNoContentHeight()) { 2960 float increasedPaddingAmount = child.getIncreasedPaddingAmount(); 2961 float padding; 2962 if (increasedPaddingAmount >= 0.0f) { 2963 padding = (int) NotificationUtils.interpolate( 2964 previousPaddingRequest, 2965 mIncreasedPaddingBetweenElements, 2966 increasedPaddingAmount); 2967 previousPaddingRequest = (int) NotificationUtils.interpolate( 2968 mPaddingBetweenElements, 2969 mIncreasedPaddingBetweenElements, 2970 increasedPaddingAmount); 2971 } else { 2972 int ownPadding = (int) NotificationUtils.interpolate( 2973 0, 2974 mPaddingBetweenElements, 2975 1.0f + increasedPaddingAmount); 2976 if (previousPaddingAmount > 0.0f) { 2977 padding = (int) NotificationUtils.interpolate( 2978 ownPadding, 2979 mIncreasedPaddingBetweenElements, 2980 previousPaddingAmount); 2981 } else { 2982 padding = ownPadding; 2983 } 2984 previousPaddingRequest = ownPadding; 2985 } 2986 if (position != 0) { 2987 position += padding; 2988 } 2989 previousPaddingAmount = increasedPaddingAmount; 2990 } 2991 if (child == requestedView) { 2992 if (requestedRow != null) { 2993 position += requestedRow.getPositionOfChild(childInGroup); 2994 } 2995 return position; 2996 } 2997 if (notGone) { 2998 position += getIntrinsicHeight(child); 2999 } 3000 } 3001 return 0; 3002 } 3003 3004 @Override 3005 public void onViewAdded(View child) { 3006 super.onViewAdded(child); 3007 onViewAddedInternal(child); 3008 } 3009 3010 private void updateFirstAndLastBackgroundViews() { 3011 ActivatableNotificationView firstChild = getFirstChildWithBackground(); 3012 ActivatableNotificationView lastChild = getLastChildWithBackground(); 3013 if (mAnimationsEnabled && mIsExpanded) { 3014 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild; 3015 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild; 3016 } else { 3017 mAnimateNextBackgroundTop = false; 3018 mAnimateNextBackgroundBottom = false; 3019 } 3020 mFirstVisibleBackgroundChild = firstChild; 3021 mLastVisibleBackgroundChild = lastChild; 3022 mAmbientState.setLastVisibleBackgroundChild(lastChild); 3023 mRoundnessManager.setFirstAndLastBackgroundChild(mFirstVisibleBackgroundChild, 3024 mLastVisibleBackgroundChild); 3025 invalidate(); 3026 } 3027 3028 private void onViewAddedInternal(View child) { 3029 updateHideSensitiveForChild(child); 3030 ((ExpandableView) child).setOnHeightChangedListener(this); 3031 generateAddAnimation(child, false /* fromMoreCard */); 3032 updateAnimationState(child); 3033 updateChronometerForChild(child); 3034 } 3035 3036 private void updateHideSensitiveForChild(View child) { 3037 if (child instanceof ExpandableView) { 3038 ExpandableView expandableView = (ExpandableView) child; 3039 expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive()); 3040 } 3041 } 3042 3043 @Override 3044 public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) { 3045 onViewRemovedInternal(row, childrenContainer); 3046 } 3047 3048 @Override 3049 public void notifyGroupChildAdded(View row) { 3050 onViewAddedInternal(row); 3051 } 3052 3053 public void setAnimationsEnabled(boolean animationsEnabled) { 3054 mAnimationsEnabled = animationsEnabled; 3055 updateNotificationAnimationStates(); 3056 if (!animationsEnabled) { 3057 mSwipedOutViews.clear(); 3058 mChildrenToRemoveAnimated.clear(); 3059 clearTemporaryViewsInGroup(this); 3060 } 3061 } 3062 3063 private void updateNotificationAnimationStates() { 3064 boolean running = mAnimationsEnabled || hasPulsingNotifications(); 3065 mShelf.setAnimationsEnabled(running); 3066 int childCount = getChildCount(); 3067 for (int i = 0; i < childCount; i++) { 3068 View child = getChildAt(i); 3069 running &= mIsExpanded || isPinnedHeadsUp(child); 3070 updateAnimationState(running, child); 3071 } 3072 } 3073 3074 private void updateAnimationState(View child) { 3075 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications()) 3076 && (mIsExpanded || isPinnedHeadsUp(child)), child); 3077 } 3078 3079 @Override 3080 public void setExpandingNotification(ExpandableNotificationRow row) { 3081 mAmbientState.setExpandingNotification(row); 3082 requestChildrenUpdate(); 3083 } 3084 3085 @Override 3086 public void bindRow(ExpandableNotificationRow row) { 3087 row.setHeadsUpAnimatingAwayListener(animatingAway -> { 3088 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway); 3089 mHeadsUpAppearanceController.updateHeader(row.getEntry()); 3090 }); 3091 } 3092 3093 @Override 3094 public void applyExpandAnimationParams(ExpandAnimationParameters params) { 3095 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange()); 3096 requestChildrenUpdate(); 3097 } 3098 3099 private void updateAnimationState(boolean running, View child) { 3100 if (child instanceof ExpandableNotificationRow) { 3101 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3102 row.setIconAnimationRunning(running); 3103 } 3104 } 3105 3106 public boolean isAddOrRemoveAnimationPending() { 3107 return mNeedsAnimation 3108 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 3109 } 3110 3111 @Override 3112 public void generateAddAnimation(View child, boolean fromMoreCard) { 3113 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { 3114 // Generate Animations 3115 mChildrenToAddAnimated.add(child); 3116 if (fromMoreCard) { 3117 mFromMoreCardAdditions.add(child); 3118 } 3119 mNeedsAnimation = true; 3120 } 3121 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress) { 3122 mAddedHeadsUpChildren.add(child); 3123 mChildrenToAddAnimated.remove(child); 3124 } 3125 } 3126 3127 @Override 3128 public void changeViewPosition(View child, int newIndex) { 3129 int currentIndex = indexOfChild(child); 3130 3131 if (currentIndex == -1) { 3132 boolean isTransient = false; 3133 if (child instanceof ExpandableNotificationRow 3134 && ((ExpandableNotificationRow)child).getTransientContainer() != null) { 3135 isTransient = true; 3136 } 3137 Log.e(TAG, "Attempting to re-position " 3138 + (isTransient ? "transient" : "") 3139 + " view {" 3140 + child 3141 + "}"); 3142 return; 3143 } 3144 3145 if (child != null && child.getParent() == this && currentIndex != newIndex) { 3146 mChangePositionInProgress = true; 3147 ((ExpandableView)child).setChangingPosition(true); 3148 removeView(child); 3149 addView(child, newIndex); 3150 ((ExpandableView)child).setChangingPosition(false); 3151 mChangePositionInProgress = false; 3152 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 3153 mChildrenChangingPositions.add(child); 3154 mNeedsAnimation = true; 3155 } 3156 } 3157 } 3158 3159 private void startAnimationToState() { 3160 if (mNeedsAnimation) { 3161 generateChildHierarchyEvents(); 3162 mNeedsAnimation = false; 3163 } 3164 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 3165 setAnimationRunning(true); 3166 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState, 3167 mGoToFullShadeDelay); 3168 mAnimationEvents.clear(); 3169 updateBackground(); 3170 updateViewShadows(); 3171 updateClippingToTopRoundedCorner(); 3172 } else { 3173 applyCurrentState(); 3174 } 3175 mGoToFullShadeDelay = 0; 3176 } 3177 3178 private void generateChildHierarchyEvents() { 3179 generateHeadsUpAnimationEvents(); 3180 generateChildRemovalEvents(); 3181 generateChildAdditionEvents(); 3182 generatePositionChangeEvents(); 3183 generateSnapBackEvents(); 3184 generateDragEvents(); 3185 generateTopPaddingEvent(); 3186 generateActivateEvent(); 3187 generateDimmedEvent(); 3188 generateHideSensitiveEvent(); 3189 generateDarkEvent(); 3190 generateGoToFullShadeEvent(); 3191 generateViewResizeEvent(); 3192 generateGroupExpansionEvent(); 3193 generateAnimateEverythingEvent(); 3194 generatePulsingAnimationEvent(); 3195 mNeedsAnimation = false; 3196 } 3197 3198 private void generateHeadsUpAnimationEvents() { 3199 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 3200 ExpandableNotificationRow row = eventPair.first; 3201 boolean isHeadsUp = eventPair.second; 3202 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; 3203 boolean onBottom = false; 3204 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded; 3205 if (!mIsExpanded && !isHeadsUp) { 3206 type = row.wasJustClicked() 3207 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 3208 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; 3209 if (row.isChildInGroup()) { 3210 // We can otherwise get stuck in there if it was just isolated 3211 row.setHeadsUpAnimatingAway(false); 3212 continue; 3213 } 3214 } else { 3215 ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row); 3216 if (viewState == null) { 3217 // A view state was never generated for this view, so we don't need to animate 3218 // this. This may happen with notification children. 3219 continue; 3220 } 3221 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { 3222 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { 3223 // Our custom add animation 3224 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 3225 } else { 3226 // Normal add animation 3227 type = AnimationEvent.ANIMATION_TYPE_ADD; 3228 } 3229 onBottom = !pinnedAndClosed; 3230 } 3231 } 3232 AnimationEvent event = new AnimationEvent(row, type); 3233 event.headsUpFromBottom = onBottom; 3234 mAnimationEvents.add(event); 3235 } 3236 mHeadsUpChangeAnimations.clear(); 3237 mAddedHeadsUpChildren.clear(); 3238 } 3239 3240 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { 3241 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { 3242 return false; 3243 } 3244 return true; 3245 } 3246 3247 private void generateGroupExpansionEvent() { 3248 // Generate a group expansion/collapsing event if there is such a group at all 3249 if (mExpandedGroupView != null) { 3250 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, 3251 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); 3252 mExpandedGroupView = null; 3253 } 3254 } 3255 3256 private void generateViewResizeEvent() { 3257 if (mNeedViewResizeAnimation) { 3258 mAnimationEvents.add( 3259 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); 3260 } 3261 mNeedViewResizeAnimation = false; 3262 } 3263 3264 private void generateSnapBackEvents() { 3265 for (View child : mSnappedBackChildren) { 3266 mAnimationEvents.add(new AnimationEvent(child, 3267 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 3268 } 3269 mSnappedBackChildren.clear(); 3270 } 3271 3272 private void generateDragEvents() { 3273 for (View child : mDragAnimPendingChildren) { 3274 mAnimationEvents.add(new AnimationEvent(child, 3275 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 3276 } 3277 mDragAnimPendingChildren.clear(); 3278 } 3279 3280 private void generateChildRemovalEvents() { 3281 for (View child : mChildrenToRemoveAnimated) { 3282 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 3283 3284 // we need to know the view after this one 3285 float removedTranslation = child.getTranslationY(); 3286 boolean ignoreChildren = true; 3287 if (child instanceof ExpandableNotificationRow) { 3288 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3289 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) { 3290 removedTranslation = row.getTranslationWhenRemoved(); 3291 ignoreChildren = false; 3292 } 3293 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); 3294 } 3295 if (!childWasSwipedOut) { 3296 Rect clipBounds = child.getClipBounds(); 3297 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; 3298 3299 if (childWasSwipedOut && child instanceof ExpandableView) { 3300 // Clean up any potential transient views if the child has already been swiped 3301 // out, as we won't be animating it further (due to its height already being 3302 // clipped to 0. 3303 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer(); 3304 if (transientContainer != null) { 3305 transientContainer.removeTransientView(child); 3306 } 3307 } 3308 } 3309 int animationType = childWasSwipedOut 3310 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 3311 : AnimationEvent.ANIMATION_TYPE_REMOVE; 3312 AnimationEvent event = new AnimationEvent(child, animationType); 3313 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation, 3314 ignoreChildren); 3315 mAnimationEvents.add(event); 3316 mSwipedOutViews.remove(child); 3317 } 3318 mChildrenToRemoveAnimated.clear(); 3319 } 3320 3321 private void generatePositionChangeEvents() { 3322 for (View child : mChildrenChangingPositions) { 3323 mAnimationEvents.add(new AnimationEvent(child, 3324 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 3325 } 3326 mChildrenChangingPositions.clear(); 3327 if (mGenerateChildOrderChangedEvent) { 3328 mAnimationEvents.add(new AnimationEvent(null, 3329 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 3330 mGenerateChildOrderChangedEvent = false; 3331 } 3332 } 3333 3334 private void generateChildAdditionEvents() { 3335 for (View child : mChildrenToAddAnimated) { 3336 if (mFromMoreCardAdditions.contains(child)) { 3337 mAnimationEvents.add(new AnimationEvent(child, 3338 AnimationEvent.ANIMATION_TYPE_ADD, 3339 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 3340 } else { 3341 mAnimationEvents.add(new AnimationEvent(child, 3342 AnimationEvent.ANIMATION_TYPE_ADD)); 3343 } 3344 } 3345 mChildrenToAddAnimated.clear(); 3346 mFromMoreCardAdditions.clear(); 3347 } 3348 3349 private void generateTopPaddingEvent() { 3350 if (mTopPaddingNeedsAnimation) { 3351 AnimationEvent event; 3352 if (mAmbientState.isDark()) { 3353 event = new AnimationEvent(null /* view */, 3354 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED, 3355 KeyguardSliceView.DEFAULT_ANIM_DURATION); 3356 } else { 3357 event = new AnimationEvent(null /* view */, 3358 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED); 3359 } 3360 mAnimationEvents.add(event); 3361 } 3362 mTopPaddingNeedsAnimation = false; 3363 } 3364 3365 private void generateActivateEvent() { 3366 if (mActivateNeedsAnimation) { 3367 mAnimationEvents.add( 3368 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 3369 } 3370 mActivateNeedsAnimation = false; 3371 } 3372 3373 private void generateAnimateEverythingEvent() { 3374 if (mEverythingNeedsAnimation) { 3375 mAnimationEvents.add( 3376 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 3377 } 3378 mEverythingNeedsAnimation = false; 3379 } 3380 3381 private void generateDimmedEvent() { 3382 if (mDimmedNeedsAnimation) { 3383 mAnimationEvents.add( 3384 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 3385 } 3386 mDimmedNeedsAnimation = false; 3387 } 3388 3389 private void generateHideSensitiveEvent() { 3390 if (mHideSensitiveNeedsAnimation) { 3391 mAnimationEvents.add( 3392 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 3393 } 3394 mHideSensitiveNeedsAnimation = false; 3395 } 3396 3397 private void generateDarkEvent() { 3398 if (mDarkNeedsAnimation) { 3399 AnimationEvent ev = new AnimationEvent(null, 3400 AnimationEvent.ANIMATION_TYPE_DARK, 3401 new AnimationFilter() 3402 .animateDark() 3403 .animateY(mShelf)); 3404 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex; 3405 mAnimationEvents.add(ev); 3406 startDarkAmountAnimation(); 3407 } 3408 mDarkNeedsAnimation = false; 3409 } 3410 3411 private void generateGoToFullShadeEvent() { 3412 if (mGoToFullShadeNeedsAnimation) { 3413 mAnimationEvents.add( 3414 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 3415 } 3416 mGoToFullShadeNeedsAnimation = false; 3417 } 3418 3419 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 3420 if (!isScrollingEnabled()) { 3421 return false; 3422 } 3423 /* 3424 * This method JUST determines whether we want to intercept the motion. 3425 * If we return true, onMotionEvent will be called and we do the actual 3426 * scrolling there. 3427 */ 3428 3429 /* 3430 * Shortcut the most recurring case: the user is in the dragging 3431 * state and is moving their finger. We want to intercept this 3432 * motion. 3433 */ 3434 final int action = ev.getAction(); 3435 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 3436 return true; 3437 } 3438 3439 switch (action & MotionEvent.ACTION_MASK) { 3440 case MotionEvent.ACTION_MOVE: { 3441 /* 3442 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 3443 * whether the user has moved far enough from the original down touch. 3444 */ 3445 3446 /* 3447 * Locally do absolute value. mLastMotionY is set to the y value 3448 * of the down event. 3449 */ 3450 final int activePointerId = mActivePointerId; 3451 if (activePointerId == INVALID_POINTER) { 3452 // If we don't have a valid id, the touch down wasn't on content. 3453 break; 3454 } 3455 3456 final int pointerIndex = ev.findPointerIndex(activePointerId); 3457 if (pointerIndex == -1) { 3458 Log.e(TAG, "Invalid pointerId=" + activePointerId 3459 + " in onInterceptTouchEvent"); 3460 break; 3461 } 3462 3463 final int y = (int) ev.getY(pointerIndex); 3464 final int x = (int) ev.getX(pointerIndex); 3465 final int yDiff = Math.abs(y - mLastMotionY); 3466 final int xDiff = Math.abs(x - mDownX); 3467 if (yDiff > mTouchSlop && yDiff > xDiff) { 3468 setIsBeingDragged(true); 3469 mLastMotionY = y; 3470 mDownX = x; 3471 initVelocityTrackerIfNotExists(); 3472 mVelocityTracker.addMovement(ev); 3473 } 3474 break; 3475 } 3476 3477 case MotionEvent.ACTION_DOWN: { 3478 final int y = (int) ev.getY(); 3479 mScrolledToTopOnFirstDown = isScrolledToTop(); 3480 if (getChildAtPosition(ev.getX(), y, false /* requireMinHeight */) == null) { 3481 setIsBeingDragged(false); 3482 recycleVelocityTracker(); 3483 break; 3484 } 3485 3486 /* 3487 * Remember location of down touch. 3488 * ACTION_DOWN always refers to pointer index 0. 3489 */ 3490 mLastMotionY = y; 3491 mDownX = (int) ev.getX(); 3492 mActivePointerId = ev.getPointerId(0); 3493 3494 initOrResetVelocityTracker(); 3495 mVelocityTracker.addMovement(ev); 3496 /* 3497 * If being flinged and user touches the screen, initiate drag; 3498 * otherwise don't. mScroller.isFinished should be false when 3499 * being flinged. 3500 */ 3501 boolean isBeingDragged = !mScroller.isFinished(); 3502 setIsBeingDragged(isBeingDragged); 3503 break; 3504 } 3505 3506 case MotionEvent.ACTION_CANCEL: 3507 case MotionEvent.ACTION_UP: 3508 /* Release the drag */ 3509 setIsBeingDragged(false); 3510 mActivePointerId = INVALID_POINTER; 3511 recycleVelocityTracker(); 3512 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 3513 animateScroll(); 3514 } 3515 break; 3516 case MotionEvent.ACTION_POINTER_UP: 3517 onSecondaryPointerUp(ev); 3518 break; 3519 } 3520 3521 /* 3522 * The only time we want to intercept motion events is if we are in the 3523 * drag mode. 3524 */ 3525 return mIsBeingDragged; 3526 } 3527 3528 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { 3529 return new StackScrollAlgorithm(context); 3530 } 3531 3532 /** 3533 * @return Whether the specified motion event is actually happening over the content. 3534 */ 3535 private boolean isInContentBounds(MotionEvent event) { 3536 return isInContentBounds(event.getY()); 3537 } 3538 3539 /** 3540 * @return Whether a y coordinate is inside the content. 3541 */ 3542 public boolean isInContentBounds(float y) { 3543 return y < getHeight() - getEmptyBottomMargin(); 3544 } 3545 3546 private void setIsBeingDragged(boolean isDragged) { 3547 mIsBeingDragged = isDragged; 3548 if (isDragged) { 3549 requestDisallowInterceptTouchEvent(true); 3550 cancelLongPress(); 3551 } 3552 } 3553 3554 @Override 3555 public void onWindowFocusChanged(boolean hasWindowFocus) { 3556 super.onWindowFocusChanged(hasWindowFocus); 3557 if (!hasWindowFocus) { 3558 cancelLongPress(); 3559 } 3560 } 3561 3562 @Override 3563 public void clearChildFocus(View child) { 3564 super.clearChildFocus(child); 3565 if (mForcedScroll == child) { 3566 mForcedScroll = null; 3567 } 3568 } 3569 3570 public void requestDisallowLongPress() { 3571 cancelLongPress(); 3572 } 3573 3574 public void requestDisallowDismiss() { 3575 mDisallowDismissInThisMotion = true; 3576 } 3577 3578 public void cancelLongPress() { 3579 mSwipeHelper.cancelLongPress(); 3580 } 3581 3582 @Override 3583 public boolean isScrolledToTop() { 3584 return mOwnScrollY == 0; 3585 } 3586 3587 @Override 3588 public boolean isScrolledToBottom() { 3589 return mOwnScrollY >= getScrollRange(); 3590 } 3591 3592 @Override 3593 public View getHostView() { 3594 return this; 3595 } 3596 3597 public int getEmptyBottomMargin() { 3598 return Math.max(mMaxLayoutHeight - mContentHeight, 0); 3599 } 3600 3601 public void checkSnoozeLeavebehind() { 3602 if (mCheckForLeavebehind) { 3603 mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, 3604 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 3605 false /* resetMenu */); 3606 mCheckForLeavebehind = false; 3607 } 3608 } 3609 3610 public void resetCheckSnoozeLeavebehind() { 3611 mCheckForLeavebehind = true; 3612 } 3613 3614 public void onExpansionStarted() { 3615 mIsExpansionChanging = true; 3616 mAmbientState.setExpansionChanging(true); 3617 checkSnoozeLeavebehind(); 3618 } 3619 3620 public void onExpansionStopped() { 3621 mIsExpansionChanging = false; 3622 resetCheckSnoozeLeavebehind(); 3623 mAmbientState.setExpansionChanging(false); 3624 if (!mIsExpanded) { 3625 setOwnScrollY(0); 3626 mStatusBar.resetUserExpandedStates(); 3627 clearTemporaryViews(); 3628 clearUserLockedViews(); 3629 } 3630 } 3631 3632 private void clearUserLockedViews() { 3633 for (int i = 0; i < getChildCount(); i++) { 3634 ExpandableView child = (ExpandableView) getChildAt(i); 3635 if (child instanceof ExpandableNotificationRow) { 3636 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3637 row.setUserLocked(false); 3638 } 3639 } 3640 } 3641 3642 private void clearTemporaryViews() { 3643 // lets make sure nothing is transient anymore 3644 clearTemporaryViewsInGroup(this); 3645 for (int i = 0; i < getChildCount(); i++) { 3646 ExpandableView child = (ExpandableView) getChildAt(i); 3647 if (child instanceof ExpandableNotificationRow) { 3648 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3649 clearTemporaryViewsInGroup(row.getChildrenContainer()); 3650 } 3651 } 3652 } 3653 3654 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) { 3655 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { 3656 viewGroup.removeTransientView(viewGroup.getTransientView(0)); 3657 } 3658 } 3659 3660 public void onPanelTrackingStarted() { 3661 mPanelTracking = true; 3662 mAmbientState.setPanelTracking(true); 3663 } 3664 public void onPanelTrackingStopped() { 3665 mPanelTracking = false; 3666 mAmbientState.setPanelTracking(false); 3667 } 3668 3669 public void resetScrollPosition() { 3670 mScroller.abortAnimation(); 3671 setOwnScrollY(0); 3672 } 3673 3674 private void setIsExpanded(boolean isExpanded) { 3675 boolean changed = isExpanded != mIsExpanded; 3676 mIsExpanded = isExpanded; 3677 mStackScrollAlgorithm.setIsExpanded(isExpanded); 3678 if (changed) { 3679 if (!mIsExpanded) { 3680 mGroupManager.collapseAllGroups(); 3681 mExpandHelper.cancelImmediately(); 3682 } 3683 updateNotificationAnimationStates(); 3684 updateChronometers(); 3685 requestChildrenUpdate(); 3686 } 3687 } 3688 3689 private void updateChronometers() { 3690 int childCount = getChildCount(); 3691 for (int i = 0; i < childCount; i++) { 3692 updateChronometerForChild(getChildAt(i)); 3693 } 3694 } 3695 3696 private void updateChronometerForChild(View child) { 3697 if (child instanceof ExpandableNotificationRow) { 3698 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3699 row.setChronometerRunning(mIsExpanded); 3700 } 3701 } 3702 3703 @Override 3704 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 3705 updateContentHeight(); 3706 updateScrollPositionOnExpandInBottom(view); 3707 clampScrollPosition(); 3708 notifyHeightChangeListener(view, needsAnimation); 3709 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow 3710 ? (ExpandableNotificationRow) view 3711 : null; 3712 if (row != null && (row == mFirstVisibleBackgroundChild 3713 || row.getNotificationParent() == mFirstVisibleBackgroundChild)) { 3714 updateAlgorithmLayoutMinHeight(); 3715 } 3716 if (needsAnimation) { 3717 requestAnimationOnViewResize(row); 3718 } 3719 requestChildrenUpdate(); 3720 } 3721 3722 @Override 3723 public void onReset(ExpandableView view) { 3724 updateAnimationState(view); 3725 updateChronometerForChild(view); 3726 } 3727 3728 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 3729 if (view instanceof ExpandableNotificationRow && !onKeyguard()) { 3730 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3731 if (row.isUserLocked() && row != getFirstChildNotGone()) { 3732 if (row.isSummaryWithChildren()) { 3733 return; 3734 } 3735 // We are actually expanding this view 3736 float endPosition = row.getTranslationY() + row.getActualHeight(); 3737 if (row.isChildInGroup()) { 3738 endPosition += row.getNotificationParent().getTranslationY(); 3739 } 3740 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation; 3741 if (row != mLastVisibleBackgroundChild && mShelf.getVisibility() != GONE) { 3742 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; 3743 } 3744 if (endPosition > layoutEnd) { 3745 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); 3746 mDisallowScrollingInThisMotion = true; 3747 } 3748 } 3749 } 3750 } 3751 3752 public void setOnHeightChangedListener( 3753 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 3754 this.mOnHeightChangedListener = mOnHeightChangedListener; 3755 } 3756 3757 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 3758 mOnEmptySpaceClickListener = listener; 3759 } 3760 3761 public void onChildAnimationFinished() { 3762 setAnimationRunning(false); 3763 requestChildrenUpdate(); 3764 runAnimationFinishedRunnables(); 3765 clearTransient(); 3766 clearHeadsUpDisappearRunning(); 3767 } 3768 3769 private void clearHeadsUpDisappearRunning() { 3770 for (int i = 0; i < getChildCount(); i++) { 3771 View view = getChildAt(i); 3772 if (view instanceof ExpandableNotificationRow) { 3773 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3774 row.setHeadsUpAnimatingAway(false); 3775 if (row.isSummaryWithChildren()) { 3776 for (ExpandableNotificationRow child : row.getNotificationChildren()) { 3777 child.setHeadsUpAnimatingAway(false); 3778 } 3779 } 3780 } 3781 } 3782 } 3783 3784 private void clearTransient() { 3785 for (ExpandableView view : mClearTransientViewsWhenFinished) { 3786 StackStateAnimator.removeTransientView(view); 3787 } 3788 mClearTransientViewsWhenFinished.clear(); 3789 } 3790 3791 private void runAnimationFinishedRunnables() { 3792 for (Runnable runnable : mAnimationFinishedRunnables) { 3793 runnable.run(); 3794 } 3795 mAnimationFinishedRunnables.clear(); 3796 } 3797 3798 /** 3799 * See {@link AmbientState#setDimmed}. 3800 */ 3801 public void setDimmed(boolean dimmed, boolean animate) { 3802 dimmed &= onKeyguard(); 3803 mAmbientState.setDimmed(dimmed); 3804 if (animate && mAnimationsEnabled) { 3805 mDimmedNeedsAnimation = true; 3806 mNeedsAnimation = true; 3807 animateDimmed(dimmed); 3808 } else { 3809 setDimAmount(dimmed ? 1.0f : 0.0f); 3810 } 3811 requestChildrenUpdate(); 3812 } 3813 3814 @VisibleForTesting 3815 boolean isDimmed() { 3816 return mAmbientState.isDimmed(); 3817 } 3818 3819 private void setDimAmount(float dimAmount) { 3820 mDimAmount = dimAmount; 3821 updateBackgroundDimming(); 3822 } 3823 3824 private void animateDimmed(boolean dimmed) { 3825 if (mDimAnimator != null) { 3826 mDimAnimator.cancel(); 3827 } 3828 float target = dimmed ? 1.0f : 0.0f; 3829 if (target == mDimAmount) { 3830 return; 3831 } 3832 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target); 3833 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED); 3834 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 3835 mDimAnimator.addListener(mDimEndListener); 3836 mDimAnimator.addUpdateListener(mDimUpdateListener); 3837 mDimAnimator.start(); 3838 } 3839 3840 public void setHideSensitive(boolean hideSensitive, boolean animate) { 3841 if (hideSensitive != mAmbientState.isHideSensitive()) { 3842 int childCount = getChildCount(); 3843 for (int i = 0; i < childCount; i++) { 3844 ExpandableView v = (ExpandableView) getChildAt(i); 3845 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 3846 } 3847 mAmbientState.setHideSensitive(hideSensitive); 3848 if (animate && mAnimationsEnabled) { 3849 mHideSensitiveNeedsAnimation = true; 3850 mNeedsAnimation = true; 3851 } 3852 updateContentHeight(); 3853 requestChildrenUpdate(); 3854 } 3855 } 3856 3857 /** 3858 * See {@link AmbientState#setActivatedChild}. 3859 */ 3860 public void setActivatedChild(ActivatableNotificationView activatedChild) { 3861 mAmbientState.setActivatedChild(activatedChild); 3862 if (mAnimationsEnabled) { 3863 mActivateNeedsAnimation = true; 3864 mNeedsAnimation = true; 3865 } 3866 requestChildrenUpdate(); 3867 } 3868 3869 public ActivatableNotificationView getActivatedChild() { 3870 return mAmbientState.getActivatedChild(); 3871 } 3872 3873 private void applyCurrentState() { 3874 mCurrentStackScrollState.apply(); 3875 if (mListener != null) { 3876 mListener.onChildLocationsChanged(); 3877 } 3878 runAnimationFinishedRunnables(); 3879 setAnimationRunning(false); 3880 updateBackground(); 3881 updateViewShadows(); 3882 updateClippingToTopRoundedCorner(); 3883 } 3884 3885 private void updateViewShadows() { 3886 // we need to work around an issue where the shadow would not cast between siblings when 3887 // their z difference is between 0 and 0.1 3888 3889 // Lefts first sort by Z difference 3890 for (int i = 0; i < getChildCount(); i++) { 3891 ExpandableView child = (ExpandableView) getChildAt(i); 3892 if (child.getVisibility() != GONE) { 3893 mTmpSortedChildren.add(child); 3894 } 3895 } 3896 Collections.sort(mTmpSortedChildren, mViewPositionComparator); 3897 3898 // Now lets update the shadow for the views 3899 ExpandableView previous = null; 3900 for (int i = 0; i < mTmpSortedChildren.size(); i++) { 3901 ExpandableView expandableView = mTmpSortedChildren.get(i); 3902 float translationZ = expandableView.getTranslationZ(); 3903 float otherZ = previous == null ? translationZ : previous.getTranslationZ(); 3904 float diff = otherZ - translationZ; 3905 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) { 3906 // There is no fake shadow to be drawn 3907 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 3908 } else { 3909 float yLocation = previous.getTranslationY() + previous.getActualHeight() - 3910 expandableView.getTranslationY() - previous.getExtraBottomPadding(); 3911 expandableView.setFakeShadowIntensity( 3912 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, 3913 previous.getOutlineAlpha(), (int) yLocation, 3914 previous.getOutlineTranslation()); 3915 } 3916 previous = expandableView; 3917 } 3918 3919 mTmpSortedChildren.clear(); 3920 } 3921 3922 /** 3923 * Update colors of "dismiss" and "empty shade" views. 3924 * 3925 * @param lightTheme True if light theme should be used. 3926 */ 3927 public void updateDecorViews(boolean lightTheme) { 3928 if (lightTheme == mUsingLightTheme) { 3929 return; 3930 } 3931 mUsingLightTheme = lightTheme; 3932 Context context = new ContextThemeWrapper(mContext, 3933 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI); 3934 final int textColor = Utils.getColorAttr(context, R.attr.wallpaperTextColor); 3935 mFooterView.setTextColor(textColor); 3936 mEmptyShadeView.setTextColor(textColor); 3937 } 3938 3939 public void goToFullShade(long delay) { 3940 mGoToFullShadeNeedsAnimation = true; 3941 mGoToFullShadeDelay = delay; 3942 mNeedsAnimation = true; 3943 requestChildrenUpdate(); 3944 } 3945 3946 public void cancelExpandHelper() { 3947 mExpandHelper.cancel(); 3948 } 3949 3950 public void setIntrinsicPadding(int intrinsicPadding) { 3951 mIntrinsicPadding = intrinsicPadding; 3952 mAmbientState.setIntrinsicPadding(intrinsicPadding); 3953 } 3954 3955 public int getIntrinsicPadding() { 3956 return mIntrinsicPadding; 3957 } 3958 3959 /** 3960 * @return the y position of the first notification 3961 */ 3962 public float getNotificationsTopY() { 3963 return mTopPadding + getStackTranslation(); 3964 } 3965 3966 @Override 3967 public boolean shouldDelayChildPressedState() { 3968 return true; 3969 } 3970 3971 /** 3972 * See {@link AmbientState#setDark}. 3973 */ 3974 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) { 3975 if (mAmbientState.isDark() == dark) { 3976 return; 3977 } 3978 mAmbientState.setDark(dark); 3979 if (animate && mAnimationsEnabled) { 3980 mDarkNeedsAnimation = true; 3981 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); 3982 mNeedsAnimation = true; 3983 } else { 3984 if (mDarkAmountAnimator != null) { 3985 mDarkAmountAnimator.cancel(); 3986 } 3987 setDarkAmount(dark ? 1f : 0f); 3988 updateBackground(); 3989 } 3990 requestChildrenUpdate(); 3991 applyCurrentBackgroundBounds(); 3992 updateWillNotDraw(); 3993 notifyHeightChangeListener(mShelf); 3994 } 3995 3996 private void updateAntiBurnInTranslation() { 3997 setTranslationX(mAntiBurnInOffsetX * mDarkAmount); 3998 } 3999 4000 /** 4001 * Updates whether or not this Layout will perform its own custom drawing (i.e. whether or 4002 * not {@link #onDraw(Canvas)} is called). This method should be called whenever the 4003 * {@link #mAmbientState}'s dark mode is toggled. 4004 */ 4005 private void updateWillNotDraw() { 4006 boolean willDraw = mShouldDrawNotificationBackground || DEBUG; 4007 setWillNotDraw(!willDraw); 4008 } 4009 4010 private void setDarkAmount(float darkAmount) { 4011 mDarkAmount = darkAmount; 4012 boolean wasFullyDark = mAmbientState.isFullyDark(); 4013 mAmbientState.setDarkAmount(darkAmount); 4014 if (mAmbientState.isFullyDark() != wasFullyDark) { 4015 updateContentHeight(); 4016 DozeParameters dozeParameters = DozeParameters.getInstance(mContext); 4017 if (mAmbientState.isFullyDark() && dozeParameters.shouldControlScreenOff()) { 4018 mShelf.fadeInTranslating(); 4019 } 4020 } 4021 updateAlgorithmHeightAndPadding(); 4022 updateBackgroundDimming(); 4023 updateAntiBurnInTranslation(); 4024 requestChildrenUpdate(); 4025 } 4026 4027 public float getDarkAmount() { 4028 return mDarkAmount; 4029 } 4030 4031 private void startDarkAmountAnimation() { 4032 ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount, 4033 mAmbientState.isDark() ? 1f : 0); 4034 darkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); 4035 darkAnimator.setInterpolator(Interpolators.ALPHA_IN); 4036 darkAnimator.addListener(new AnimatorListenerAdapter() { 4037 @Override 4038 public void onAnimationEnd(Animator animation) { 4039 mDarkAmountAnimator = null; 4040 } 4041 }); 4042 if (mDarkAmountAnimator != null) { 4043 mDarkAmountAnimator.cancel(); 4044 } 4045 mDarkAmountAnimator = darkAnimator; 4046 mDarkAmountAnimator.start(); 4047 } 4048 4049 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) { 4050 if (screenLocation == null || screenLocation.y < mTopPadding) { 4051 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 4052 } 4053 if (screenLocation.y > getBottomMostNotificationBottom()) { 4054 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW; 4055 } 4056 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y); 4057 if (child != null) { 4058 return getNotGoneIndex(child); 4059 } else { 4060 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 4061 } 4062 } 4063 4064 private int getNotGoneIndex(View child) { 4065 int count = getChildCount(); 4066 int notGoneIndex = 0; 4067 for (int i = 0; i < count; i++) { 4068 View v = getChildAt(i); 4069 if (child == v) { 4070 return notGoneIndex; 4071 } 4072 if (v.getVisibility() != View.GONE) { 4073 notGoneIndex++; 4074 } 4075 } 4076 return -1; 4077 } 4078 4079 public void setFooterView(@NonNull FooterView footerView) { 4080 int index = -1; 4081 if (mFooterView != null) { 4082 index = indexOfChild(mFooterView); 4083 removeView(mFooterView); 4084 } 4085 mFooterView = footerView; 4086 addView(mFooterView, index); 4087 } 4088 4089 public void setEmptyShadeView(EmptyShadeView emptyShadeView) { 4090 int index = -1; 4091 if (mEmptyShadeView != null) { 4092 index = indexOfChild(mEmptyShadeView); 4093 removeView(mEmptyShadeView); 4094 } 4095 mEmptyShadeView = emptyShadeView; 4096 addView(mEmptyShadeView, index); 4097 } 4098 4099 public void updateEmptyShadeView(boolean visible) { 4100 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); 4101 4102 int oldTextRes = mEmptyShadeView.getTextResource(); 4103 int newTextRes = mStatusBar.areNotificationsHidden() 4104 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text; 4105 if (oldTextRes != newTextRes) { 4106 mEmptyShadeView.setText(newTextRes); 4107 } 4108 } 4109 4110 public void updateFooterView(boolean visible, boolean showDismissView) { 4111 if (mFooterView == null) { 4112 return; 4113 } 4114 boolean animate = mIsExpanded && mAnimationsEnabled; 4115 mFooterView.setVisible(visible, animate); 4116 mFooterView.setSecondaryVisible(showDismissView, animate); 4117 } 4118 4119 public void setDismissAllInProgress(boolean dismissAllInProgress) { 4120 mDismissAllInProgress = dismissAllInProgress; 4121 mAmbientState.setDismissAllInProgress(dismissAllInProgress); 4122 handleDismissAllClipping(); 4123 } 4124 4125 private void handleDismissAllClipping() { 4126 final int count = getChildCount(); 4127 boolean previousChildWillBeDismissed = false; 4128 for (int i = 0; i < count; i++) { 4129 ExpandableView child = (ExpandableView) getChildAt(i); 4130 if (child.getVisibility() == GONE) { 4131 continue; 4132 } 4133 if (mDismissAllInProgress && previousChildWillBeDismissed) { 4134 child.setMinClipTopAmount(child.getClipTopAmount()); 4135 } else { 4136 child.setMinClipTopAmount(0); 4137 } 4138 previousChildWillBeDismissed = canChildBeDismissed(child); 4139 } 4140 } 4141 4142 public boolean isFooterViewNotGone() { 4143 return mFooterView != null 4144 && mFooterView.getVisibility() != View.GONE 4145 && !mFooterView.willBeGone(); 4146 } 4147 4148 public boolean isFooterViewContentVisible() { 4149 return mFooterView != null && mFooterView.isContentVisible(); 4150 } 4151 4152 public int getFooterViewHeight() { 4153 return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements; 4154 } 4155 4156 public int getEmptyShadeViewHeight() { 4157 return mEmptyShadeView.getHeight(); 4158 } 4159 4160 public float getBottomMostNotificationBottom() { 4161 final int count = getChildCount(); 4162 float max = 0; 4163 for (int childIdx = 0; childIdx < count; childIdx++) { 4164 ExpandableView child = (ExpandableView) getChildAt(childIdx); 4165 if (child.getVisibility() == GONE) { 4166 continue; 4167 } 4168 float bottom = child.getTranslationY() + child.getActualHeight() 4169 - child.getClipBottomAmount(); 4170 if (bottom > max) { 4171 max = bottom; 4172 } 4173 } 4174 return max + getStackTranslation(); 4175 } 4176 4177 public void setStatusBar(StatusBar statusBar) { 4178 this.mStatusBar = statusBar; 4179 } 4180 4181 public void setGroupManager(NotificationGroupManager groupManager) { 4182 this.mGroupManager = groupManager; 4183 } 4184 4185 public void onGoToKeyguard() { 4186 requestAnimateEverything(); 4187 } 4188 4189 private void requestAnimateEverything() { 4190 if (mIsExpanded && mAnimationsEnabled) { 4191 mEverythingNeedsAnimation = true; 4192 mNeedsAnimation = true; 4193 requestChildrenUpdate(); 4194 } 4195 } 4196 4197 public boolean isBelowLastNotification(float touchX, float touchY) { 4198 int childCount = getChildCount(); 4199 for (int i = childCount - 1; i >= 0; i--) { 4200 ExpandableView child = (ExpandableView) getChildAt(i); 4201 if (child.getVisibility() != View.GONE) { 4202 float childTop = child.getY(); 4203 if (childTop > touchY) { 4204 // we are above a notification entirely let's abort 4205 return false; 4206 } 4207 boolean belowChild = touchY > childTop + child.getActualHeight() 4208 - child.getClipBottomAmount(); 4209 if (child == mFooterView) { 4210 if(!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(), 4211 touchY - childTop)) { 4212 // We clicked on the dismiss button 4213 return false; 4214 } 4215 } else if (child == mEmptyShadeView) { 4216 // We arrived at the empty shade view, for which we accept all clicks 4217 return true; 4218 } else if (!belowChild){ 4219 // We are on a child 4220 return false; 4221 } 4222 } 4223 } 4224 return touchY > mTopPadding + mStackTranslation; 4225 } 4226 4227 @Override 4228 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { 4229 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled 4230 && (mIsExpanded || changedRow.isPinned()); 4231 if (animated) { 4232 mExpandedGroupView = changedRow; 4233 mNeedsAnimation = true; 4234 } 4235 changedRow.setChildrenExpanded(expanded, animated); 4236 if (!mGroupExpandedForMeasure) { 4237 onHeightChanged(changedRow, false /* needsAnimation */); 4238 } 4239 runAfterAnimationFinished(new Runnable() { 4240 @Override 4241 public void run() { 4242 changedRow.onFinishedExpansionChange(); 4243 } 4244 }); 4245 } 4246 4247 @Override 4248 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { 4249 mStatusBar.requestNotificationUpdate(); 4250 } 4251 4252 /** @hide */ 4253 @Override 4254 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 4255 super.onInitializeAccessibilityEventInternal(event); 4256 event.setScrollable(mScrollable); 4257 event.setScrollX(mScrollX); 4258 event.setScrollY(mOwnScrollY); 4259 event.setMaxScrollX(mScrollX); 4260 event.setMaxScrollY(getScrollRange()); 4261 } 4262 4263 @Override 4264 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 4265 super.onInitializeAccessibilityNodeInfoInternal(info); 4266 if (mScrollable) { 4267 info.setScrollable(true); 4268 if (mBackwardScrollable) { 4269 info.addAction( 4270 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 4271 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); 4272 } 4273 if (mForwardScrollable) { 4274 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 4275 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN); 4276 } 4277 } 4278 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview 4279 info.setClassName(ScrollView.class.getName()); 4280 } 4281 4282 /** @hide */ 4283 @Override 4284 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 4285 if (super.performAccessibilityActionInternal(action, arguments)) { 4286 return true; 4287 } 4288 if (!isEnabled()) { 4289 return false; 4290 } 4291 int direction = -1; 4292 switch (action) { 4293 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 4294 // fall through 4295 case android.R.id.accessibilityActionScrollDown: 4296 direction = 1; 4297 // fall through 4298 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 4299 // fall through 4300 case android.R.id.accessibilityActionScrollUp: 4301 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop 4302 - mShelf.getIntrinsicHeight(); 4303 final int targetScrollY = Math.max(0, 4304 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); 4305 if (targetScrollY != mOwnScrollY) { 4306 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY); 4307 animateScroll(); 4308 return true; 4309 } 4310 break; 4311 } 4312 return false; 4313 } 4314 4315 @Override 4316 public void onGroupsChanged() { 4317 mStatusBar.requestNotificationUpdate(); 4318 } 4319 4320 public void generateChildOrderChangedEvent() { 4321 if (mIsExpanded && mAnimationsEnabled) { 4322 mGenerateChildOrderChangedEvent = true; 4323 mNeedsAnimation = true; 4324 requestChildrenUpdate(); 4325 } 4326 } 4327 4328 @Override 4329 public int getContainerChildCount() { 4330 return getChildCount(); 4331 } 4332 4333 @Override 4334 public View getContainerChildAt(int i) { 4335 return getChildAt(i); 4336 } 4337 4338 @Override 4339 public void removeContainerView(View v) { 4340 removeView(v); 4341 } 4342 4343 @Override 4344 public void addContainerView(View v) { 4345 addView(v); 4346 } 4347 4348 public void runAfterAnimationFinished(Runnable runnable) { 4349 mAnimationFinishedRunnables.add(runnable); 4350 } 4351 4352 public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { 4353 mHeadsUpManager = headsUpManager; 4354 mAmbientState.setHeadsUpManager(headsUpManager); 4355 mHeadsUpManager.addListener(mRoundnessManager); 4356 } 4357 4358 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 4359 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) { 4360 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); 4361 mNeedsAnimation = true; 4362 if (!mIsExpanded && !isHeadsUp) { 4363 row.setHeadsUpAnimatingAway(true); 4364 } 4365 requestChildrenUpdate(); 4366 } 4367 } 4368 4369 public void setShadeExpanded(boolean shadeExpanded) { 4370 mAmbientState.setShadeExpanded(shadeExpanded); 4371 mStateAnimator.setShadeExpanded(shadeExpanded); 4372 } 4373 4374 /** 4375 * Set the boundary for the bottom heads up position. The heads up will always be above this 4376 * position. 4377 * 4378 * @param height the height of the screen 4379 * @param bottomBarHeight the height of the bar on the bottom 4380 */ 4381 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 4382 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); 4383 mStateAnimator.setHeadsUpAppearHeightBottom(height); 4384 requestChildrenUpdate(); 4385 } 4386 4387 public void setTrackingHeadsUp(ExpandableNotificationRow row) { 4388 mTrackingHeadsUp = row != null; 4389 mRoundnessManager.setTrackingHeadsUp(row); 4390 } 4391 4392 public void setScrimController(ScrimController scrimController) { 4393 mScrimController = scrimController; 4394 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming); 4395 } 4396 4397 public void forceNoOverlappingRendering(boolean force) { 4398 mForceNoOverlappingRendering = force; 4399 } 4400 4401 @Override 4402 public boolean hasOverlappingRendering() { 4403 return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); 4404 } 4405 4406 public void setAnimationRunning(boolean animationRunning) { 4407 if (animationRunning != mAnimationRunning) { 4408 if (animationRunning) { 4409 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater); 4410 } else { 4411 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater); 4412 } 4413 mAnimationRunning = animationRunning; 4414 updateContinuousShadowDrawing(); 4415 } 4416 } 4417 4418 public boolean isExpanded() { 4419 return mIsExpanded; 4420 } 4421 4422 public void setPulsing(boolean pulsing, boolean animated) { 4423 if (!mPulsing && !pulsing) { 4424 return; 4425 } 4426 mPulsing = pulsing; 4427 mNeedingPulseAnimation = animated ? getFirstChildNotGone() : null; 4428 mAmbientState.setPulsing(pulsing); 4429 updateNotificationAnimationStates(); 4430 updateAlgorithmHeightAndPadding(); 4431 updateContentHeight(); 4432 requestChildrenUpdate(); 4433 notifyHeightChangeListener(null, animated); 4434 mNeedsAnimation |= animated; 4435 } 4436 4437 private void generatePulsingAnimationEvent() { 4438 if (mNeedingPulseAnimation != null) { 4439 int type = mPulsing ? AnimationEvent.ANIMATION_TYPE_PULSE_APPEAR 4440 : AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR; 4441 mAnimationEvents.add(new AnimationEvent(mNeedingPulseAnimation, type)); 4442 mNeedingPulseAnimation = null; 4443 } 4444 } 4445 4446 public void setFadingOut(boolean fadingOut) { 4447 if (fadingOut != mFadingOut) { 4448 mFadingOut = fadingOut; 4449 updateFadingState(); 4450 } 4451 } 4452 4453 public void setParentNotFullyVisible(boolean parentNotFullyVisible) { 4454 if (mScrimController == null) { 4455 // we're not set up yet. 4456 return; 4457 } 4458 if (parentNotFullyVisible != mParentNotFullyVisible) { 4459 mParentNotFullyVisible = parentNotFullyVisible; 4460 updateFadingState(); 4461 } 4462 } 4463 4464 private void updateFadingState() { 4465 applyCurrentBackgroundBounds(); 4466 updateSrcDrawing(); 4467 } 4468 4469 @Override 4470 public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) { 4471 super.setAlpha(alpha); 4472 setFadingOut(alpha != 1.0f); 4473 } 4474 4475 public void setQsExpanded(boolean qsExpanded) { 4476 mQsExpanded = qsExpanded; 4477 updateAlgorithmLayoutMinHeight(); 4478 updateScrollability(); 4479 } 4480 4481 public void setQsExpansionFraction(float qsExpansionFraction) { 4482 mQsExpansionFraction = qsExpansionFraction; 4483 } 4484 4485 public void setOwnScrollY(int ownScrollY) { 4486 if (ownScrollY != mOwnScrollY) { 4487 // We still want to call the normal scrolled changed for accessibility reasons 4488 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); 4489 mOwnScrollY = ownScrollY; 4490 updateForwardAndBackwardScrollability(); 4491 requestChildrenUpdate(); 4492 } 4493 } 4494 4495 public void setShelf(NotificationShelf shelf) { 4496 int index = -1; 4497 if (mShelf != null) { 4498 index = indexOfChild(mShelf); 4499 removeView(mShelf); 4500 } 4501 mShelf = shelf; 4502 addView(mShelf, index); 4503 mAmbientState.setShelf(shelf); 4504 mStateAnimator.setShelf(shelf); 4505 shelf.bind(mAmbientState, this); 4506 } 4507 4508 public NotificationShelf getNotificationShelf() { 4509 return mShelf; 4510 } 4511 4512 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) { 4513 if (mMaxDisplayedNotifications != maxDisplayedNotifications) { 4514 mMaxDisplayedNotifications = maxDisplayedNotifications; 4515 updateContentHeight(); 4516 notifyHeightChangeListener(mShelf); 4517 } 4518 } 4519 4520 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) { 4521 mShouldShowShelfOnly = shouldShowShelfOnly; 4522 updateAlgorithmLayoutMinHeight(); 4523 } 4524 4525 public int getMinExpansionHeight() { 4526 return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2; 4527 } 4528 4529 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) { 4530 mInHeadsUpPinnedMode = inHeadsUpPinnedMode; 4531 updateClipping(); 4532 } 4533 4534 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 4535 mHeadsUpAnimatingAway = headsUpAnimatingAway; 4536 updateClipping(); 4537 } 4538 4539 public void setStatusBarState(int statusBarState) { 4540 mStatusBarState = statusBarState; 4541 mAmbientState.setStatusBarState(statusBarState); 4542 } 4543 4544 public void setExpandingVelocity(float expandingVelocity) { 4545 mAmbientState.setExpandingVelocity(expandingVelocity); 4546 } 4547 4548 public float getOpeningHeight() { 4549 if (mEmptyShadeView.getVisibility() == GONE) { 4550 return getMinExpansionHeight(); 4551 } else { 4552 return getAppearEndPosition(); 4553 } 4554 } 4555 4556 public void setIsFullWidth(boolean isFullWidth) { 4557 mAmbientState.setPanelFullWidth(isFullWidth); 4558 } 4559 4560 public void setUnlockHintRunning(boolean running) { 4561 mAmbientState.setUnlockHintRunning(running); 4562 } 4563 4564 public void setQsCustomizerShowing(boolean isShowing) { 4565 mAmbientState.setQsCustomizerShowing(isShowing); 4566 requestChildrenUpdate(); 4567 } 4568 4569 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) { 4570 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; 4571 } 4572 4573 public void setAntiBurnInOffsetX(int antiBurnInOffsetX) { 4574 mAntiBurnInOffsetX = antiBurnInOffsetX; 4575 updateAntiBurnInTranslation(); 4576 } 4577 4578 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 4579 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" 4580 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s" 4581 + " qsExpandFraction=%f]", 4582 this.getClass().getSimpleName(), 4583 mPulsing ? "T":"f", 4584 mAmbientState.isQsCustomizerShowing() ? "T":"f", 4585 getVisibility() == View.VISIBLE ? "visible" 4586 : getVisibility() == View.GONE ? "gone" 4587 : "invisible", 4588 getAlpha(), 4589 mAmbientState.getScrollY(), 4590 mMaxTopPadding, 4591 mShouldShowShelfOnly ? "T":"f", 4592 mQsExpansionFraction)); 4593 } 4594 4595 public boolean isFullyDark() { 4596 return mAmbientState.isFullyDark(); 4597 } 4598 4599 /** 4600 * Add a listener whenever the expanded height changes. The first value passed as an argument 4601 * is the expanded height and the second one is the appearFraction. 4602 * 4603 * @param listener the listener to notify. 4604 */ 4605 public void addOnExpandedHeightListener(BiConsumer<Float, Float> listener) { 4606 mExpandedHeightListeners.add(listener); 4607 } 4608 4609 /** 4610 * Stop a listener from listening to the expandedHeight. 4611 */ 4612 public void removeOnExpandedHeightListener(BiConsumer<Float, Float> listener) { 4613 mExpandedHeightListeners.remove(listener); 4614 } 4615 4616 public void setHeadsUpAppearanceController( 4617 HeadsUpAppearanceController headsUpAppearanceController) { 4618 mHeadsUpAppearanceController = headsUpAppearanceController; 4619 } 4620 4621 /** 4622 * A listener that is notified when the empty space below the notifications is clicked on 4623 */ 4624 public interface OnEmptySpaceClickListener { 4625 void onEmptySpaceClicked(float x, float y); 4626 } 4627 4628 /** 4629 * A listener that gets notified when the overscroll at the top has changed. 4630 */ 4631 public interface OnOverscrollTopChangedListener { 4632 4633 /** 4634 * Notifies a listener that the overscroll has changed. 4635 * 4636 * @param amount the amount of overscroll, in pixels 4637 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 4638 * unrubberbanded motion to directly expand overscroll view (e.g expand 4639 * QS) 4640 */ 4641 void onOverscrollTopChanged(float amount, boolean isRubberbanded); 4642 4643 /** 4644 * Notify a listener that the scroller wants to escape from the scrolling motion and 4645 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 4646 * 4647 * @param velocity The velocity that the Scroller had when over flinging 4648 * @param open Should the fling open or close the overscroll view. 4649 */ 4650 void flingTopOverscroll(float velocity, boolean open); 4651 } 4652 4653 private class NotificationSwipeHelper extends SwipeHelper 4654 implements NotificationSwipeActionHelper { 4655 private static final long COVER_MENU_DELAY = 4000; 4656 private Runnable mFalsingCheck; 4657 private Handler mHandler; 4658 4659 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { 4660 super(swipeDirection, callback, context); 4661 mHandler = new Handler(); 4662 mFalsingCheck = new Runnable() { 4663 @Override 4664 public void run() { 4665 resetExposedMenuView(true /* animate */, true /* force */); 4666 } 4667 }; 4668 } 4669 4670 @Override 4671 public void onDownUpdate(View currView, MotionEvent ev) { 4672 mTranslatingParentView = currView; 4673 if (mCurrMenuRow != null) { 4674 mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */); 4675 } 4676 mCurrMenuRow = null; 4677 mHandler.removeCallbacks(mFalsingCheck); 4678 4679 // Slide back any notifications that might be showing a menu 4680 resetExposedMenuView(true /* animate */, false /* force */); 4681 4682 if (currView instanceof ExpandableNotificationRow) { 4683 ExpandableNotificationRow row = (ExpandableNotificationRow) currView; 4684 mCurrMenuRow = row.createMenu(); 4685 mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this); 4686 mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this); 4687 mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */); 4688 } 4689 } 4690 4691 @Override 4692 public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) { 4693 mHandler.removeCallbacks(mFalsingCheck); 4694 if (mCurrMenuRow != null) { 4695 mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */); 4696 } 4697 } 4698 4699 @Override 4700 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, 4701 float translation) { 4702 if (mCurrMenuRow != null) { 4703 return mCurrMenuRow.onTouchEvent(animView, ev, velocity); 4704 } 4705 return false; 4706 } 4707 4708 @Override 4709 public void dismissChild(final View view, float velocity, 4710 boolean useAccelerateInterpolator) { 4711 super.dismissChild(view, velocity, useAccelerateInterpolator); 4712 if (mIsExpanded) { 4713 // We don't want to quick-dismiss when it's a heads up as this might lead to closing 4714 // of the panel early. 4715 handleChildViewDismissed(view); 4716 } 4717 mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, 4718 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 4719 false /* resetMenu */); 4720 handleMenuCoveredOrDismissed(); 4721 } 4722 4723 @Override 4724 public void snapChild(final View animView, final float targetLeft, float velocity) { 4725 super.snapChild(animView, targetLeft, velocity); 4726 onDragCancelled(animView); 4727 if (targetLeft == 0) { 4728 handleMenuCoveredOrDismissed(); 4729 } 4730 } 4731 4732 @Override 4733 public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) { 4734 mStatusBar.setNotificationSnoozed(sbn, snoozeOption); 4735 } 4736 4737 public boolean isFalseGesture(MotionEvent ev) { 4738 return super.isFalseGesture(ev); 4739 } 4740 4741 private void handleMenuCoveredOrDismissed() { 4742 if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) { 4743 mMenuExposedView = null; 4744 } 4745 } 4746 4747 @Override 4748 public Animator getViewTranslationAnimator(View v, float target, 4749 AnimatorUpdateListener listener) { 4750 if (v instanceof ExpandableNotificationRow) { 4751 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); 4752 } else { 4753 return super.getViewTranslationAnimator(v, target, listener); 4754 } 4755 } 4756 4757 @Override 4758 public void setTranslation(View v, float translate) { 4759 ((ExpandableView) v).setTranslation(translate); 4760 } 4761 4762 @Override 4763 public float getTranslation(View v) { 4764 return ((ExpandableView) v).getTranslation(); 4765 } 4766 4767 @Override 4768 public void dismiss(View animView, float velocity) { 4769 dismissChild(animView, velocity, 4770 !swipedFastEnough(0, 0) /* useAccelerateInterpolator */); 4771 } 4772 4773 @Override 4774 public void snap(View animView, float targetLeft, float velocity) { 4775 snapChild(animView, targetLeft, velocity); 4776 } 4777 4778 @Override 4779 public boolean swipedFarEnough(float translation, float viewSize) { 4780 return swipedFarEnough(); 4781 } 4782 4783 @Override 4784 public boolean swipedFastEnough(float translation, float velocity) { 4785 return swipedFastEnough(); 4786 } 4787 4788 @Override 4789 public float getMinDismissVelocity() { 4790 return getEscapeVelocity(); 4791 } 4792 4793 public void onMenuShown(View animView) { 4794 onDragCancelled(animView); 4795 4796 // If we're on the lockscreen we want to false this. 4797 if (isAntiFalsingNeeded()) { 4798 mHandler.removeCallbacks(mFalsingCheck); 4799 mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY); 4800 } 4801 } 4802 4803 public void closeControlsIfOutsideTouch(MotionEvent ev) { 4804 NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); 4805 View view = null; 4806 if (guts != null && !guts.getGutsContent().isLeavebehind()) { 4807 // Only close visible guts if they're not a leavebehind. 4808 view = guts; 4809 } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible() 4810 && mTranslatingParentView != null) { 4811 // Checking menu 4812 view = mTranslatingParentView; 4813 } 4814 if (view != null && !isTouchInView(ev, view)) { 4815 // Touch was outside visible guts / menu notification, close what's visible 4816 mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */, 4817 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, 4818 false /* resetMenu */); 4819 resetExposedMenuView(true /* animate */, true /* force */); 4820 } 4821 } 4822 4823 public void resetExposedMenuView(boolean animate, boolean force) { 4824 if (mMenuExposedView == null 4825 || (!force && mMenuExposedView == mTranslatingParentView)) { 4826 // If no menu is showing or it's showing for this view we do nothing. 4827 return; 4828 } 4829 final View prevMenuExposedView = mMenuExposedView; 4830 if (animate) { 4831 Animator anim = getViewTranslationAnimator(prevMenuExposedView, 4832 0 /* leftTarget */, null /* updateListener */); 4833 if (anim != null) { 4834 anim.start(); 4835 } 4836 } else if (mMenuExposedView instanceof ExpandableNotificationRow) { 4837 ExpandableNotificationRow row = (ExpandableNotificationRow) mMenuExposedView; 4838 if (!row.isRemoved()) { 4839 row.resetTranslation(); 4840 } 4841 } 4842 mMenuExposedView = null; 4843 } 4844 } 4845 4846 private boolean isTouchInView(MotionEvent ev, View view) { 4847 if (view == null) { 4848 return false; 4849 } 4850 final int height = (view instanceof ExpandableView) 4851 ? ((ExpandableView) view).getActualHeight() 4852 : view.getHeight(); 4853 final int rx = (int) ev.getRawX(); 4854 final int ry = (int) ev.getRawY(); 4855 view.getLocationOnScreen(mTempInt2); 4856 final int x = mTempInt2[0]; 4857 final int y = mTempInt2[1]; 4858 Rect rect = new Rect(x, y, x + view.getWidth(), y + height); 4859 boolean ret = rect.contains(rx, ry); 4860 return ret; 4861 } 4862 4863 private void updateContinuousShadowDrawing() { 4864 boolean continuousShadowUpdate = mAnimationRunning 4865 || !mAmbientState.getDraggedViews().isEmpty(); 4866 if (continuousShadowUpdate != mContinuousShadowUpdate) { 4867 if (continuousShadowUpdate) { 4868 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater); 4869 } else { 4870 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater); 4871 } 4872 mContinuousShadowUpdate = continuousShadowUpdate; 4873 } 4874 } 4875 4876 @Override 4877 public void resetExposedMenuView(boolean animate, boolean force) { 4878 mSwipeHelper.resetExposedMenuView(animate, force); 4879 } 4880 4881 public void closeControlsIfOutsideTouch(MotionEvent ev) { 4882 mSwipeHelper.closeControlsIfOutsideTouch(ev); 4883 } 4884 4885 static class AnimationEvent { 4886 4887 static AnimationFilter[] FILTERS = new AnimationFilter[] { 4888 4889 // ANIMATION_TYPE_ADD 4890 new AnimationFilter() 4891 .animateShadowAlpha() 4892 .animateHeight() 4893 .animateTopInset() 4894 .animateY() 4895 .animateZ() 4896 .hasDelays(), 4897 4898 // ANIMATION_TYPE_REMOVE 4899 new AnimationFilter() 4900 .animateShadowAlpha() 4901 .animateHeight() 4902 .animateTopInset() 4903 .animateY() 4904 .animateZ() 4905 .hasDelays(), 4906 4907 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 4908 new AnimationFilter() 4909 .animateShadowAlpha() 4910 .animateHeight() 4911 .animateTopInset() 4912 .animateY() 4913 .animateZ() 4914 .hasDelays(), 4915 4916 // ANIMATION_TYPE_TOP_PADDING_CHANGED 4917 new AnimationFilter() 4918 .animateShadowAlpha() 4919 .animateHeight() 4920 .animateTopInset() 4921 .animateY() 4922 .animateDimmed() 4923 .animateZ(), 4924 4925 // ANIMATION_TYPE_START_DRAG 4926 new AnimationFilter() 4927 .animateShadowAlpha(), 4928 4929 // ANIMATION_TYPE_SNAP_BACK 4930 new AnimationFilter() 4931 .animateShadowAlpha() 4932 .animateHeight(), 4933 4934 // ANIMATION_TYPE_ACTIVATED_CHILD 4935 new AnimationFilter() 4936 .animateZ(), 4937 4938 // ANIMATION_TYPE_DIMMED 4939 new AnimationFilter() 4940 .animateDimmed(), 4941 4942 // ANIMATION_TYPE_CHANGE_POSITION 4943 new AnimationFilter() 4944 .animateAlpha() // maybe the children change positions 4945 .animateShadowAlpha() 4946 .animateHeight() 4947 .animateTopInset() 4948 .animateY() 4949 .animateZ(), 4950 4951 // ANIMATION_TYPE_DARK 4952 null, // Unused 4953 4954 // ANIMATION_TYPE_GO_TO_FULL_SHADE 4955 new AnimationFilter() 4956 .animateShadowAlpha() 4957 .animateHeight() 4958 .animateTopInset() 4959 .animateY() 4960 .animateDimmed() 4961 .animateZ() 4962 .hasDelays(), 4963 4964 // ANIMATION_TYPE_HIDE_SENSITIVE 4965 new AnimationFilter() 4966 .animateHideSensitive(), 4967 4968 // ANIMATION_TYPE_VIEW_RESIZE 4969 new AnimationFilter() 4970 .animateShadowAlpha() 4971 .animateHeight() 4972 .animateTopInset() 4973 .animateY() 4974 .animateZ(), 4975 4976 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 4977 new AnimationFilter() 4978 .animateAlpha() 4979 .animateShadowAlpha() 4980 .animateHeight() 4981 .animateTopInset() 4982 .animateY() 4983 .animateZ(), 4984 4985 // ANIMATION_TYPE_HEADS_UP_APPEAR 4986 new AnimationFilter() 4987 .animateShadowAlpha() 4988 .animateHeight() 4989 .animateTopInset() 4990 .animateY() 4991 .animateZ(), 4992 4993 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 4994 new AnimationFilter() 4995 .animateShadowAlpha() 4996 .animateHeight() 4997 .animateTopInset() 4998 .animateY() 4999 .animateZ() 5000 .hasDelays(), 5001 5002 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 5003 new AnimationFilter() 5004 .animateShadowAlpha() 5005 .animateHeight() 5006 .animateTopInset() 5007 .animateY() 5008 .animateZ() 5009 .hasDelays(), 5010 5011 // ANIMATION_TYPE_HEADS_UP_OTHER 5012 new AnimationFilter() 5013 .animateShadowAlpha() 5014 .animateHeight() 5015 .animateTopInset() 5016 .animateY() 5017 .animateZ(), 5018 5019 // ANIMATION_TYPE_EVERYTHING 5020 new AnimationFilter() 5021 .animateAlpha() 5022 .animateShadowAlpha() 5023 .animateDark() 5024 .animateDimmed() 5025 .animateHideSensitive() 5026 .animateHeight() 5027 .animateTopInset() 5028 .animateY() 5029 .animateZ(), 5030 5031 // ANIMATION_TYPE_PULSE_APPEAR 5032 new AnimationFilter() 5033 .animateAlpha() 5034 .animateY(), 5035 5036 // ANIMATION_TYPE_PULSE_DISAPPEAR 5037 new AnimationFilter() 5038 .animateAlpha() 5039 .animateY(), 5040 }; 5041 5042 static int[] LENGTHS = new int[] { 5043 5044 // ANIMATION_TYPE_ADD 5045 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 5046 5047 // ANIMATION_TYPE_REMOVE 5048 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 5049 5050 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 5051 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5052 5053 // ANIMATION_TYPE_TOP_PADDING_CHANGED 5054 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5055 5056 // ANIMATION_TYPE_START_DRAG 5057 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5058 5059 // ANIMATION_TYPE_SNAP_BACK 5060 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5061 5062 // ANIMATION_TYPE_ACTIVATED_CHILD 5063 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 5064 5065 // ANIMATION_TYPE_DIMMED 5066 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 5067 5068 // ANIMATION_TYPE_CHANGE_POSITION 5069 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5070 5071 // ANIMATION_TYPE_DARK 5072 StackStateAnimator.ANIMATION_DURATION_WAKEUP, 5073 5074 // ANIMATION_TYPE_GO_TO_FULL_SHADE 5075 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 5076 5077 // ANIMATION_TYPE_HIDE_SENSITIVE 5078 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5079 5080 // ANIMATION_TYPE_VIEW_RESIZE 5081 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5082 5083 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 5084 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5085 5086 // ANIMATION_TYPE_HEADS_UP_APPEAR 5087 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, 5088 5089 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 5090 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 5091 5092 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 5093 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 5094 5095 // ANIMATION_TYPE_HEADS_UP_OTHER 5096 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5097 5098 // ANIMATION_TYPE_EVERYTHING 5099 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5100 5101 // ANIMATION_TYPE_PULSE_APPEAR 5102 KeyguardSliceView.DEFAULT_ANIM_DURATION, 5103 5104 // ANIMATION_TYPE_PULSE_DISAPPEAR 5105 KeyguardSliceView.DEFAULT_ANIM_DURATION / 2, 5106 }; 5107 5108 static final int ANIMATION_TYPE_ADD = 0; 5109 static final int ANIMATION_TYPE_REMOVE = 1; 5110 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 5111 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 5112 static final int ANIMATION_TYPE_START_DRAG = 4; 5113 static final int ANIMATION_TYPE_SNAP_BACK = 5; 5114 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 5115 static final int ANIMATION_TYPE_DIMMED = 7; 5116 static final int ANIMATION_TYPE_CHANGE_POSITION = 8; 5117 static final int ANIMATION_TYPE_DARK = 9; 5118 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; 5119 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; 5120 static final int ANIMATION_TYPE_VIEW_RESIZE = 12; 5121 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; 5122 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14; 5123 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15; 5124 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16; 5125 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17; 5126 static final int ANIMATION_TYPE_EVERYTHING = 18; 5127 static final int ANIMATION_TYPE_PULSE_APPEAR = 19; 5128 static final int ANIMATION_TYPE_PULSE_DISAPPEAR = 20; 5129 5130 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; 5131 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; 5132 5133 final long eventStartTime; 5134 final View changingView; 5135 final int animationType; 5136 final AnimationFilter filter; 5137 final long length; 5138 View viewAfterChangingView; 5139 int darkAnimationOriginIndex; 5140 boolean headsUpFromBottom; 5141 5142 AnimationEvent(View view, int type) { 5143 this(view, type, LENGTHS[type]); 5144 } 5145 5146 AnimationEvent(View view, int type, AnimationFilter filter) { 5147 this(view, type, LENGTHS[type], filter); 5148 } 5149 5150 AnimationEvent(View view, int type, long length) { 5151 this(view, type, length, FILTERS[type]); 5152 } 5153 5154 AnimationEvent(View view, int type, long length, AnimationFilter filter) { 5155 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 5156 changingView = view; 5157 animationType = type; 5158 this.length = length; 5159 this.filter = filter; 5160 } 5161 5162 /** 5163 * Combines the length of several animation events into a single value. 5164 * 5165 * @param events The events of the lengths to combine. 5166 * @return The combined length. Depending on the event types, this might be the maximum of 5167 * all events or the length of a specific event. 5168 */ 5169 static long combineLength(ArrayList<AnimationEvent> events) { 5170 long length = 0; 5171 int size = events.size(); 5172 for (int i = 0; i < size; i++) { 5173 AnimationEvent event = events.get(i); 5174 length = Math.max(length, event.length); 5175 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 5176 return event.length; 5177 } 5178 } 5179 return length; 5180 } 5181 } 5182 } 5183