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.notification.stack; 18 19 import static android.service.notification.NotificationStats.DISMISSAL_SHADE; 20 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; 21 22 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; 23 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; 24 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; 25 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING; 26 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; 27 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY; 28 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 29 30 import static java.lang.annotation.RetentionPolicy.SOURCE; 31 32 import android.animation.Animator; 33 import android.animation.AnimatorListenerAdapter; 34 import android.animation.TimeAnimator; 35 import android.animation.ValueAnimator; 36 import android.annotation.ColorInt; 37 import android.annotation.IntDef; 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.res.Configuration; 43 import android.content.res.Resources; 44 import android.graphics.Canvas; 45 import android.graphics.Color; 46 import android.graphics.Outline; 47 import android.graphics.Paint; 48 import android.graphics.Point; 49 import android.graphics.PointF; 50 import android.graphics.PorterDuff; 51 import android.graphics.PorterDuffXfermode; 52 import android.graphics.Rect; 53 import android.os.Bundle; 54 import android.os.ServiceManager; 55 import android.os.UserHandle; 56 import android.provider.Settings; 57 import android.service.notification.NotificationListenerService; 58 import android.service.notification.StatusBarNotification; 59 import android.util.AttributeSet; 60 import android.util.DisplayMetrics; 61 import android.util.Log; 62 import android.util.MathUtils; 63 import android.util.Pair; 64 import android.view.ContextThemeWrapper; 65 import android.view.DisplayCutout; 66 import android.view.InputDevice; 67 import android.view.LayoutInflater; 68 import android.view.MotionEvent; 69 import android.view.VelocityTracker; 70 import android.view.View; 71 import android.view.ViewConfiguration; 72 import android.view.ViewGroup; 73 import android.view.ViewOutlineProvider; 74 import android.view.ViewTreeObserver; 75 import android.view.WindowInsets; 76 import android.view.accessibility.AccessibilityEvent; 77 import android.view.accessibility.AccessibilityNodeInfo; 78 import android.view.animation.AnimationUtils; 79 import android.view.animation.Interpolator; 80 import android.widget.OverScroller; 81 import android.widget.ScrollView; 82 83 import com.android.internal.annotations.VisibleForTesting; 84 import com.android.internal.graphics.ColorUtils; 85 import com.android.internal.logging.MetricsLogger; 86 import com.android.internal.logging.UiEvent; 87 import com.android.internal.logging.UiEventLogger; 88 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 89 import com.android.internal.statusbar.IStatusBarService; 90 import com.android.internal.statusbar.NotificationVisibility; 91 import com.android.keyguard.KeyguardSliceView; 92 import com.android.settingslib.Utils; 93 import com.android.systemui.Dependency; 94 import com.android.systemui.Dumpable; 95 import com.android.systemui.ExpandHelper; 96 import com.android.systemui.Interpolators; 97 import com.android.systemui.R; 98 import com.android.systemui.SwipeHelper; 99 import com.android.systemui.colorextraction.SysuiColorExtractor; 100 import com.android.systemui.media.KeyguardMediaController; 101 import com.android.systemui.plugins.FalsingManager; 102 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 103 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; 104 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; 105 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; 106 import com.android.systemui.plugins.statusbar.StatusBarStateController; 107 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; 108 import com.android.systemui.statusbar.CommandQueue; 109 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; 110 import com.android.systemui.statusbar.EmptyShadeView; 111 import com.android.systemui.statusbar.FeatureFlags; 112 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 113 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; 114 import com.android.systemui.statusbar.NotificationRemoteInputManager; 115 import com.android.systemui.statusbar.NotificationShelf; 116 import com.android.systemui.statusbar.RemoteInputController; 117 import com.android.systemui.statusbar.StatusBarState; 118 import com.android.systemui.statusbar.SysuiStatusBarStateController; 119 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 120 import com.android.systemui.statusbar.notification.FakeShadowView; 121 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; 122 import com.android.systemui.statusbar.notification.NotificationActivityStarter; 123 import com.android.systemui.statusbar.notification.NotificationEntryListener; 124 import com.android.systemui.statusbar.notification.NotificationEntryManager; 125 import com.android.systemui.statusbar.notification.NotificationUtils; 126 import com.android.systemui.statusbar.notification.ShadeViewRefactor; 127 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; 128 import com.android.systemui.statusbar.notification.VisualStabilityManager; 129 import com.android.systemui.statusbar.notification.collection.NotifCollection; 130 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 131 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 132 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; 133 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 134 import com.android.systemui.statusbar.notification.logging.NotificationLogger; 135 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 136 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 137 import com.android.systemui.statusbar.notification.row.ExpandableView; 138 import com.android.systemui.statusbar.notification.row.FooterView; 139 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView; 140 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; 141 import com.android.systemui.statusbar.notification.row.NotificationGuts; 142 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 143 import com.android.systemui.statusbar.notification.row.NotificationSnooze; 144 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; 145 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; 146 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; 147 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; 148 import com.android.systemui.statusbar.phone.KeyguardBypassController; 149 import com.android.systemui.statusbar.phone.LockscreenGestureLogger; 150 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; 151 import com.android.systemui.statusbar.phone.NotificationGroupManager; 152 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; 153 import com.android.systemui.statusbar.phone.NotificationIconAreaController; 154 import com.android.systemui.statusbar.phone.NotificationPanelViewController; 155 import com.android.systemui.statusbar.phone.ScrimController; 156 import com.android.systemui.statusbar.phone.ShadeController; 157 import com.android.systemui.statusbar.phone.StatusBar; 158 import com.android.systemui.statusbar.policy.ConfigurationController; 159 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 160 import com.android.systemui.statusbar.policy.HeadsUpUtil; 161 import com.android.systemui.statusbar.policy.ScrollAdapter; 162 import com.android.systemui.statusbar.policy.ZenModeController; 163 import com.android.systemui.tuner.TunerService; 164 import com.android.systemui.util.Assert; 165 166 import java.io.FileDescriptor; 167 import java.io.PrintWriter; 168 import java.lang.annotation.Retention; 169 import java.util.ArrayList; 170 import java.util.Collections; 171 import java.util.Comparator; 172 import java.util.HashSet; 173 import java.util.List; 174 import java.util.function.BiConsumer; 175 176 import javax.inject.Inject; 177 import javax.inject.Named; 178 179 /** 180 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 181 */ 182 public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter, 183 NotificationListContainer, ConfigurationListener, Dumpable, 184 DynamicPrivacyController.Listener { 185 186 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; 187 private static final String TAG = "StackScroller"; 188 private static final boolean DEBUG = false; 189 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 190 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 191 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 192 /** 193 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 194 */ 195 private static final int INVALID_POINTER = -1; 196 /** 197 * The distance in pixels between sections when the sections are directly adjacent (no visible 198 * gap is drawn between them). In this case we don't want to round their corners. 199 */ 200 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1; 201 private final KeyguardBypassController mKeyguardBypassController; 202 private final DynamicPrivacyController mDynamicPrivacyController; 203 private final SysuiStatusBarStateController mStatusbarStateController; 204 private final KeyguardMediaController mKeyguardMediaController; 205 206 private ExpandHelper mExpandHelper; 207 private final NotificationSwipeHelper mSwipeHelper; 208 private int mCurrentStackHeight = Integer.MAX_VALUE; 209 private final Paint mBackgroundPaint = new Paint(); 210 private final boolean mShouldDrawNotificationBackground; 211 private boolean mHighPriorityBeforeSpeedBump; 212 private final boolean mAllowLongPress; 213 private boolean mDismissRtl; 214 215 private float mExpandedHeight; 216 private int mOwnScrollY; 217 private View mScrollAnchorView; 218 private int mScrollAnchorViewY; 219 private int mMaxLayoutHeight; 220 221 private VelocityTracker mVelocityTracker; 222 private OverScroller mScroller; 223 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */ 224 private int mLastScrollerY; 225 /** 226 * True if the max position was set to a known position on the last call to {@link #mScroller}. 227 */ 228 private boolean mIsScrollerBoundSet; 229 private Runnable mFinishScrollingCallback; 230 private int mTouchSlop; 231 private float mSlopMultiplier; 232 private int mMinimumVelocity; 233 private int mMaximumVelocity; 234 private int mOverflingDistance; 235 private float mMaxOverScroll; 236 private boolean mIsBeingDragged; 237 private int mLastMotionY; 238 private int mDownX; 239 private int mActivePointerId = INVALID_POINTER; 240 private boolean mTouchIsClick; 241 private float mInitialTouchX; 242 private float mInitialTouchY; 243 244 private Paint mDebugPaint; 245 private int mContentHeight; 246 private int mIntrinsicContentHeight; 247 private int mCollapsedSize; 248 private int mPaddingBetweenElements; 249 private int mIncreasedPaddingBetweenElements; 250 private int mMaxTopPadding; 251 private int mTopPadding; 252 private int mBottomMargin; 253 private int mBottomInset = 0; 254 private float mQsExpansionFraction; 255 256 /** 257 * The algorithm which calculates the properties for our children 258 */ 259 protected final StackScrollAlgorithm mStackScrollAlgorithm; 260 261 private final AmbientState mAmbientState; 262 private NotificationGroupManager mGroupManager; 263 private NotificationActivityStarter mNotificationActivityStarter; 264 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>(); 265 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); 266 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>(); 267 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>(); 268 private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); 269 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); 270 private ArrayList<View> mSwipedOutViews = new ArrayList<>(); 271 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 272 private boolean mAnimationsEnabled; 273 private boolean mChangePositionInProgress; 274 private boolean mChildTransferInProgress; 275 276 /** 277 * The raw amount of the overScroll on the top, which is not rubber-banded. 278 */ 279 private float mOverScrolledTopPixels; 280 281 /** 282 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 283 */ 284 private float mOverScrolledBottomPixels; 285 private NotificationLogger.OnChildLocationsChangedListener mListener; 286 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 287 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 288 private OnEmptySpaceClickListener mOnEmptySpaceClickListener; 289 private boolean mNeedsAnimation; 290 private boolean mTopPaddingNeedsAnimation; 291 private boolean mDimmedNeedsAnimation; 292 private boolean mHideSensitiveNeedsAnimation; 293 private boolean mActivateNeedsAnimation; 294 private boolean mGoToFullShadeNeedsAnimation; 295 private boolean mIsExpanded = true; 296 private boolean mChildrenUpdateRequested; 297 private boolean mIsExpansionChanging; 298 private boolean mPanelTracking; 299 private boolean mExpandingNotification; 300 private boolean mExpandedInThisMotion; 301 private boolean mShouldShowShelfOnly; 302 protected boolean mScrollingEnabled; 303 protected FooterView mFooterView; 304 protected EmptyShadeView mEmptyShadeView; 305 private boolean mDismissAllInProgress; 306 private boolean mFadeNotificationsOnDismiss; 307 308 /** 309 * Was the scroller scrolled to the top when the down motion was observed? 310 */ 311 private boolean mScrolledToTopOnFirstDown; 312 /** 313 * The minimal amount of over scroll which is needed in order to switch to the quick settings 314 * when over scrolling on a expanded card. 315 */ 316 private float mMinTopOverScrollToEscape; 317 private int mIntrinsicPadding; 318 private float mStackTranslation; 319 private float mTopPaddingOverflow; 320 private boolean mDontReportNextOverScroll; 321 private boolean mDontClampNextScroll; 322 private boolean mNeedViewResizeAnimation; 323 private ExpandableView mExpandedGroupView; 324 private boolean mEverythingNeedsAnimation; 325 326 /** 327 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 328 * This is needed to avoid scrolling too far after the notification was collapsed in the same 329 * motion. 330 */ 331 private int mMaxScrollAfterExpand; 332 private ExpandableNotificationRow.LongPressListener mLongPressListener; 333 boolean mCheckForLeavebehind; 334 335 /** 336 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 337 * animating. 338 */ 339 private boolean mOnlyScrollingInThisMotion; 340 private boolean mDisallowDismissInThisMotion; 341 private boolean mDisallowScrollingInThisMotion; 342 private long mGoToFullShadeDelay; 343 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 344 = new ViewTreeObserver.OnPreDrawListener() { 345 @Override 346 public boolean onPreDraw() { 347 updateForcedScroll(); 348 updateChildren(); 349 mChildrenUpdateRequested = false; 350 getViewTreeObserver().removeOnPreDrawListener(this); 351 return true; 352 } 353 }; 354 private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() { 355 @Override 356 public void onUserChanged(int userId) { 357 updateSensitiveness(false /* animated */); 358 } 359 }; 360 private StatusBar mStatusBar; 361 private int[] mTempInt2 = new int[2]; 362 private boolean mGenerateChildOrderChangedEvent; 363 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); 364 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>(); 365 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations 366 = new HashSet<>(); 367 private HeadsUpManagerPhone mHeadsUpManager; 368 private final NotificationRoundnessManager mRoundnessManager; 369 private boolean mTrackingHeadsUp; 370 private ScrimController mScrimController; 371 private boolean mForceNoOverlappingRendering; 372 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>(); 373 private FalsingManager mFalsingManager; 374 private final ZenModeController mZenController; 375 private boolean mAnimationRunning; 376 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater 377 = new ViewTreeObserver.OnPreDrawListener() { 378 @Override 379 public boolean onPreDraw() { 380 onPreDrawDuringAnimation(); 381 return true; 382 } 383 }; 384 private NotificationSection[] mSections; 385 private boolean mAnimateNextBackgroundTop; 386 private boolean mAnimateNextBackgroundBottom; 387 private boolean mAnimateNextSectionBoundsChange; 388 private int mBgColor; 389 private float mDimAmount; 390 private ValueAnimator mDimAnimator; 391 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); 392 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { 393 @Override 394 public void onAnimationEnd(Animator animation) { 395 mDimAnimator = null; 396 } 397 }; 398 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener 399 = new ValueAnimator.AnimatorUpdateListener() { 400 401 @Override 402 public void onAnimationUpdate(ValueAnimator animation) { 403 setDimAmount((Float) animation.getAnimatedValue()); 404 } 405 }; 406 protected ViewGroup mQsContainer; 407 private boolean mContinuousShadowUpdate; 408 private boolean mContinuousBackgroundUpdate; 409 private ViewTreeObserver.OnPreDrawListener mShadowUpdater 410 = new ViewTreeObserver.OnPreDrawListener() { 411 412 @Override 413 public boolean onPreDraw() { 414 updateViewShadows(); 415 return true; 416 } 417 }; 418 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> { 419 updateBackground(); 420 return true; 421 }; 422 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() { 423 @Override 424 public int compare(ExpandableView view, ExpandableView otherView) { 425 float endY = view.getTranslationY() + view.getActualHeight(); 426 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight(); 427 if (endY < otherEndY) { 428 return -1; 429 } else if (endY > otherEndY) { 430 return 1; 431 } else { 432 // The two notifications end at the same location 433 return 0; 434 } 435 } 436 }; 437 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() { 438 @Override 439 public void getOutline(View view, Outline outline) { 440 if (mAmbientState.isHiddenAtAll()) { 441 float xProgress = mHideXInterpolator.getInterpolation( 442 (1 - mLinearHideAmount) * mBackgroundXFactor); 443 outline.setRoundRect(mBackgroundAnimationRect, 444 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius, 445 xProgress)); 446 outline.setAlpha(1.0f - mAmbientState.getHideAmount()); 447 } else { 448 ViewOutlineProvider.BACKGROUND.getOutline(view, outline); 449 } 450 } 451 }; 452 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); 453 private boolean mPulsing; 454 private boolean mGroupExpandedForMeasure; 455 private boolean mScrollable; 456 private View mForcedScroll; 457 458 /** 459 * @see #setHideAmount(float, float) 460 */ 461 private float mInterpolatedHideAmount = 0f; 462 463 /** 464 * @see #setHideAmount(float, float) 465 */ 466 private float mLinearHideAmount = 0f; 467 468 /** 469 * How fast the background scales in the X direction as a factor of the Y expansion. 470 */ 471 private float mBackgroundXFactor = 1f; 472 473 private boolean mSwipingInProgress; 474 475 private boolean mUsingLightTheme; 476 private boolean mQsExpanded; 477 private boolean mForwardScrollable; 478 private boolean mBackwardScrollable; 479 private NotificationShelf mShelf; 480 private int mMaxDisplayedNotifications = -1; 481 private int mStatusBarHeight; 482 private int mMinInteractionHeight; 483 private boolean mNoAmbient; 484 private final Rect mClipRect = new Rect(); 485 private boolean mIsClipped; 486 private Rect mRequestedClipBounds; 487 private boolean mInHeadsUpPinnedMode; 488 private boolean mHeadsUpAnimatingAway; 489 private int mStatusBarState; 490 private int mCachedBackgroundColor; 491 private boolean mHeadsUpGoingAwayAnimationsAllowed = true; 492 private Runnable mReflingAndAnimateScroll = () -> { 493 if (ANCHOR_SCROLLING) { 494 maybeReflingScroller(); 495 } 496 animateScroll(); 497 }; 498 private int mCornerRadius; 499 private int mSidePaddings; 500 private final Rect mBackgroundAnimationRect = new Rect(); 501 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); 502 private int mHeadsUpInset; 503 private HeadsUpAppearanceController mHeadsUpAppearanceController; 504 private NotificationIconAreaController mIconAreaController; 505 private final NotificationLockscreenUserManager mLockscreenUserManager; 506 private final Rect mTmpRect = new Rect(); 507 private final FeatureFlags mFeatureFlags; 508 private final NotifPipeline mNotifPipeline; 509 private final NotifCollection mNotifCollection; 510 private final NotificationEntryManager mEntryManager; 511 private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface( 512 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 513 @VisibleForTesting 514 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); 515 protected final UiEventLogger mUiEventLogger; 516 private final NotificationRemoteInputManager mRemoteInputManager = 517 Dependency.get(NotificationRemoteInputManager.class); 518 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class); 519 520 private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class); 521 private final LockscreenGestureLogger mLockscreenGestureLogger = 522 Dependency.get(LockscreenGestureLogger.class); 523 private final VisualStabilityManager mVisualStabilityManager = 524 Dependency.get(VisualStabilityManager.class); 525 protected boolean mClearAllEnabled; 526 527 private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN; 528 private NotificationPanelViewController mNotificationPanelController; 529 530 private final NotificationGutsManager mNotificationGutsManager; 531 private final NotificationSectionsManager mSectionsManager; 532 private final ForegroundServiceSectionController mFgsSectionController; 533 private ForegroundServiceDungeonView mFgsSectionView; 534 private boolean mAnimateBottomOnLayout; 535 private float mLastSentAppear; 536 private float mLastSentExpandedHeight; 537 private boolean mWillExpand; 538 private int mGapHeight; 539 540 private int mWaterfallTopInset; 541 542 private SysuiColorExtractor.OnColorsChangedListener mOnColorsChangedListener = 543 (colorExtractor, which) -> { 544 final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); 545 updateDecorViews(useDarkText); 546 }; 547 548 @Inject NotificationStackScrollLayout( @amedVIEW_CONTEXT) Context context, AttributeSet attrs, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, NotificationRoundnessManager notificationRoundnessManager, DynamicPrivacyController dynamicPrivacyController, SysuiStatusBarStateController statusBarStateController, HeadsUpManagerPhone headsUpManager, KeyguardBypassController keyguardBypassController, KeyguardMediaController keyguardMediaController, FalsingManager falsingManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGutsManager notificationGutsManager, ZenModeController zenController, NotificationSectionsManager notificationSectionsManager, ForegroundServiceSectionController fgsSectionController, ForegroundServiceDismissalFeatureController fgsFeatureController, FeatureFlags featureFlags, NotifPipeline notifPipeline, NotificationEntryManager entryManager, NotifCollection notifCollection, UiEventLogger uiEventLogger )549 public NotificationStackScrollLayout( 550 @Named(VIEW_CONTEXT) Context context, 551 AttributeSet attrs, 552 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, 553 NotificationRoundnessManager notificationRoundnessManager, 554 DynamicPrivacyController dynamicPrivacyController, 555 SysuiStatusBarStateController statusBarStateController, 556 HeadsUpManagerPhone headsUpManager, 557 KeyguardBypassController keyguardBypassController, 558 KeyguardMediaController keyguardMediaController, 559 FalsingManager falsingManager, 560 NotificationLockscreenUserManager notificationLockscreenUserManager, 561 NotificationGutsManager notificationGutsManager, 562 ZenModeController zenController, 563 NotificationSectionsManager notificationSectionsManager, 564 ForegroundServiceSectionController fgsSectionController, 565 ForegroundServiceDismissalFeatureController fgsFeatureController, 566 FeatureFlags featureFlags, 567 NotifPipeline notifPipeline, 568 NotificationEntryManager entryManager, 569 NotifCollection notifCollection, 570 UiEventLogger uiEventLogger 571 ) { 572 super(context, attrs, 0, 0); 573 Resources res = getResources(); 574 575 mAllowLongPress = allowLongPress; 576 577 mRoundnessManager = notificationRoundnessManager; 578 579 mLockscreenUserManager = notificationLockscreenUserManager; 580 mNotificationGutsManager = notificationGutsManager; 581 mHeadsUpManager = headsUpManager; 582 mHeadsUpManager.addListener(mRoundnessManager); 583 mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed); 584 mKeyguardBypassController = keyguardBypassController; 585 mFalsingManager = falsingManager; 586 mZenController = zenController; 587 mFgsSectionController = fgsSectionController; 588 589 mSectionsManager = notificationSectionsManager; 590 mSectionsManager.initialize(this, LayoutInflater.from(context)); 591 mSectionsManager.setOnClearSilentNotifsClickListener(v -> { 592 // Leave the shade open if there will be other notifs left over to clear 593 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); 594 clearNotifications(ROWS_GENTLE, closeShade); 595 }); 596 mSections = mSectionsManager.createSectionsForBuckets(); 597 598 mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager); 599 mBgColor = context.getColor(R.color.notification_shade_background_color); 600 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); 601 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); 602 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, 603 minHeight, maxHeight); 604 mExpandHelper.setEventSource(this); 605 mExpandHelper.setScrollAdapter(this); 606 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback, 607 getContext(), mMenuEventListener, mFalsingManager); 608 mStackScrollAlgorithm = createStackScrollAlgorithm(context); 609 initView(context); 610 mShouldDrawNotificationBackground = 611 res.getBoolean(R.bool.config_drawNotificationBackground); 612 mFadeNotificationsOnDismiss = 613 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); 614 mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); 615 mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); 616 addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded); 617 mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); 618 setOutlineProvider(mOutlineProvider); 619 620 // Blocking helper manager wants to know the expanded state, update as well. 621 NotificationBlockingHelperManager blockingHelperManager = 622 Dependency.get(NotificationBlockingHelperManager.class); 623 addOnExpandedHeightChangedListener((height, unused) -> { 624 blockingHelperManager.setNotificationShadeExpanded(height); 625 }); 626 627 boolean willDraw = mShouldDrawNotificationBackground || DEBUG; 628 setWillNotDraw(!willDraw); 629 mBackgroundPaint.setAntiAlias(true); 630 if (DEBUG) { 631 mDebugPaint = new Paint(); 632 mDebugPaint.setColor(0xffff0000); 633 mDebugPaint.setStrokeWidth(2); 634 mDebugPaint.setStyle(Paint.Style.STROKE); 635 mDebugPaint.setTextSize(25f); 636 } 637 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); 638 639 TunerService tunerService = Dependency.get(TunerService.class); 640 tunerService.addTunable((key, newValue) -> { 641 if (key.equals(HIGH_PRIORITY)) { 642 mHighPriorityBeforeSpeedBump = "1".equals(newValue); 643 } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) { 644 updateDismissRtlSetting("1".equals(newValue)); 645 } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) { 646 updateFooter(); 647 } 648 }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL, 649 Settings.Secure.NOTIFICATION_HISTORY_ENABLED); 650 651 mFeatureFlags = featureFlags; 652 mNotifPipeline = notifPipeline; 653 mEntryManager = entryManager; 654 mNotifCollection = notifCollection; 655 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { 656 mNotifPipeline.addCollectionListener(new NotifCollectionListener() { 657 @Override 658 public void onEntryUpdated(NotificationEntry entry) { 659 NotificationStackScrollLayout.this.onEntryUpdated(entry); 660 } 661 }); 662 } else { 663 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { 664 @Override 665 public void onPreEntryUpdated(NotificationEntry entry) { 666 NotificationStackScrollLayout.this.onEntryUpdated(entry); 667 } 668 }); 669 } 670 671 dynamicPrivacyController.addListener(this); 672 mDynamicPrivacyController = dynamicPrivacyController; 673 mStatusbarStateController = statusBarStateController; 674 initializeForegroundServiceSection(fgsFeatureController); 675 mUiEventLogger = uiEventLogger; 676 mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); 677 mKeyguardMediaController = keyguardMediaController; 678 keyguardMediaController.setVisibilityChangedListener((visible) -> { 679 if (visible) { 680 generateAddAnimation(keyguardMediaController.getView(), false /*fromMoreCard */); 681 } else { 682 generateRemoveAnimation(keyguardMediaController.getView()); 683 } 684 requestChildrenUpdate(); 685 return null; 686 }); 687 } 688 initializeForegroundServiceSection( ForegroundServiceDismissalFeatureController featureController)689 private void initializeForegroundServiceSection( 690 ForegroundServiceDismissalFeatureController featureController) { 691 if (featureController.isForegroundServiceDismissalEnabled()) { 692 LayoutInflater li = LayoutInflater.from(mContext); 693 mFgsSectionView = 694 (ForegroundServiceDungeonView) mFgsSectionController.createView(li); 695 addView(mFgsSectionView, -1); 696 } 697 } 698 updateDismissRtlSetting(boolean dismissRtl)699 private void updateDismissRtlSetting(boolean dismissRtl) { 700 mDismissRtl = dismissRtl; 701 for (int i = 0; i < getChildCount(); i++) { 702 View child = getChildAt(i); 703 if (child instanceof ExpandableNotificationRow) { 704 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); 705 } 706 } 707 } 708 709 @Override 710 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onFinishInflate()711 protected void onFinishInflate() { 712 super.onFinishInflate(); 713 714 inflateEmptyShadeView(); 715 inflateFooterView(); 716 mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation); 717 if (mAllowLongPress) { 718 setLongPressListener(mNotificationGutsManager::openGuts); 719 } 720 } 721 722 /** 723 * @return the height at which we will wake up when pulsing 724 */ getWakeUpHeight()725 public float getWakeUpHeight() { 726 ExpandableView firstChild = getFirstChildWithBackground(); 727 if (firstChild != null) { 728 if (mKeyguardBypassController.getBypassEnabled()) { 729 return firstChild.getHeadsUpHeightWithoutHeader(); 730 } else { 731 return firstChild.getCollapsedHeight(); 732 } 733 } 734 return 0f; 735 } 736 737 @Override 738 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onDensityOrFontScaleChanged()739 public void onDensityOrFontScaleChanged() { 740 reinflateViews(); 741 } 742 reinflateViews()743 private void reinflateViews() { 744 inflateFooterView(); 745 inflateEmptyShadeView(); 746 updateFooter(); 747 mSectionsManager.reinflateViews(LayoutInflater.from(mContext)); 748 } 749 750 @Override 751 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onThemeChanged()752 public void onThemeChanged() { 753 updateFooter(); 754 } 755 756 @Override onOverlayChanged()757 public void onOverlayChanged() { 758 int newRadius = mContext.getResources().getDimensionPixelSize( 759 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); 760 if (mCornerRadius != newRadius) { 761 mCornerRadius = newRadius; 762 invalidate(); 763 } 764 reinflateViews(); 765 } 766 767 @VisibleForTesting 768 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateFooter()769 public void updateFooter() { 770 if (mFooterView == null) { 771 return; 772 } 773 boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL); 774 boolean showFooterView = (showDismissView || hasActiveNotifications()) 775 && mStatusBarState != StatusBarState.KEYGUARD 776 && !mRemoteInputManager.getController().isRemoteInputActive(); 777 boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), 778 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; 779 780 updateFooterView(showFooterView, showDismissView, showHistory); 781 } 782 783 /** 784 * Return whether there are any clearable notifications 785 */ 786 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) hasActiveClearableNotifications(@electedRows int selection)787 public boolean hasActiveClearableNotifications(@SelectedRows int selection) { 788 if (mDynamicPrivacyController.isInLockedDownShade()) { 789 return false; 790 } 791 int childCount = getChildCount(); 792 for (int i = 0; i < childCount; i++) { 793 View child = getChildAt(i); 794 if (!(child instanceof ExpandableNotificationRow)) { 795 continue; 796 } 797 final ExpandableNotificationRow row = (ExpandableNotificationRow) child; 798 if (row.canViewBeDismissed() && matchesSelection(row, selection)) { 799 return true; 800 } 801 } 802 return false; 803 } 804 805 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) createDelegate()806 public RemoteInputController.Delegate createDelegate() { 807 return new RemoteInputController.Delegate() { 808 public void setRemoteInputActive(NotificationEntry entry, 809 boolean remoteInputActive) { 810 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); 811 entry.notifyHeightChanged(true /* needsAnimation */); 812 updateFooter(); 813 } 814 815 public void lockScrollTo(NotificationEntry entry) { 816 NotificationStackScrollLayout.this.lockScrollTo(entry.getRow()); 817 } 818 819 public void requestDisallowLongPressAndDismiss() { 820 requestDisallowLongPress(); 821 requestDisallowDismiss(); 822 } 823 }; 824 } 825 826 @Override 827 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 828 protected void onAttachedToWindow() { 829 super.onAttachedToWindow(); 830 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class)) 831 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER); 832 Dependency.get(ConfigurationController.class).addCallback(this); 833 } 834 835 @Override 836 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 837 protected void onDetachedFromWindow() { 838 super.onDetachedFromWindow(); 839 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); 840 Dependency.get(ConfigurationController.class).removeCallback(this); 841 } 842 843 @Override 844 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 845 public NotificationSwipeActionHelper getSwipeActionHelper() { 846 return mSwipeHelper; 847 } 848 849 @Override 850 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 851 public void onUiModeChanged() { 852 mBgColor = mContext.getColor(R.color.notification_shade_background_color); 853 updateBackgroundDimming(); 854 mShelf.onUiModeChanged(); 855 } 856 857 @ShadeViewRefactor(RefactorComponent.DECORATOR) 858 protected void onDraw(Canvas canvas) { 859 if (mShouldDrawNotificationBackground 860 && (mSections[0].getCurrentBounds().top 861 < mSections[mSections.length - 1].getCurrentBounds().bottom 862 || mAmbientState.isDozing())) { 863 drawBackground(canvas); 864 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) { 865 drawHeadsUpBackground(canvas); 866 } 867 868 if (DEBUG) { 869 int y = mTopPadding; 870 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 871 y = getLayoutHeight(); 872 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 873 y = getHeight() - getEmptyBottomMargin(); 874 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 875 } 876 } 877 878 @Override 879 public void draw(Canvas canvas) { 880 super.draw(canvas); 881 882 if (DEBUG && ANCHOR_SCROLLING) { 883 if (mScrollAnchorView instanceof ExpandableNotificationRow) { 884 canvas.drawRect(0, 885 mScrollAnchorView.getTranslationY(), 886 getWidth(), 887 mScrollAnchorView.getTranslationY() 888 + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(), 889 mDebugPaint); 890 canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200, 891 mScrollAnchorView.getTranslationY() + 30, mDebugPaint); 892 int y = (int) mShelf.getTranslationY(); 893 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 894 } 895 canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100, 896 getTopPadding() + 30, mDebugPaint); 897 canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100, 898 getHeight() - 30, mDebugPaint); 899 } 900 } 901 902 @ShadeViewRefactor(RefactorComponent.DECORATOR) 903 private void drawBackground(Canvas canvas) { 904 int lockScreenLeft = mSidePaddings; 905 int lockScreenRight = getWidth() - mSidePaddings; 906 int lockScreenTop = mSections[0].getCurrentBounds().top; 907 int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom; 908 int hiddenLeft = getWidth() / 2; 909 int hiddenTop = mTopPadding; 910 911 float yProgress = 1 - mInterpolatedHideAmount; 912 float xProgress = mHideXInterpolator.getInterpolation( 913 (1 - mLinearHideAmount) * mBackgroundXFactor); 914 915 int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress); 916 int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress); 917 int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress); 918 int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress); 919 mBackgroundAnimationRect.set( 920 left, 921 top, 922 right, 923 bottom); 924 925 int backgroundTopAnimationOffset = top - lockScreenTop; 926 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD 927 boolean anySectionHasVisibleChild = false; 928 for (NotificationSection section : mSections) { 929 if (section.needsBackground()) { 930 anySectionHasVisibleChild = true; 931 break; 932 } 933 } 934 boolean shouldDrawBackground; 935 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) { 936 shouldDrawBackground = isPulseExpanding(); 937 } else { 938 shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild; 939 } 940 if (shouldDrawBackground) { 941 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset); 942 } 943 944 updateClipping(); 945 } 946 947 /** 948 * Draws round rects for each background section. 949 * 950 * We want to draw a round rect for each background section as defined by {@link #mSections}. 951 * However, if two sections are directly adjacent with no gap between them (e.g. on the 952 * lockscreen where the shelf can appear directly below the high priority section, or while 953 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority 954 * section), we don't want to round the adjacent corners. 955 * 956 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we 957 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect. 958 * This method tracks the top of each rect we need to draw, then iterates through the visible 959 * sections. If a section is not adjacent to the previous section, we draw the previous rect 960 * behind the sections we've accumulated up to that point, then start a new rect at the top of 961 * the current section. When we're done iterating we will always have one rect left to draw. 962 */ 963 private void drawBackgroundRects(Canvas canvas, int left, int right, int top, 964 int animationYOffset) { 965 int backgroundRectTop = top; 966 int lastSectionBottom = 967 mSections[0].getCurrentBounds().bottom + animationYOffset; 968 int currentLeft = left; 969 int currentRight = right; 970 boolean first = true; 971 for (NotificationSection section : mSections) { 972 if (!section.needsBackground()) { 973 continue; 974 } 975 int sectionTop = section.getCurrentBounds().top + animationYOffset; 976 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right); 977 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft); 978 // If sections are directly adjacent to each other, we don't want to draw them 979 // as separate roundrects, as the rounded corners right next to each other look 980 // bad. 981 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX 982 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) { 983 canvas.drawRoundRect(currentLeft, 984 backgroundRectTop, 985 currentRight, 986 lastSectionBottom, 987 mCornerRadius, mCornerRadius, mBackgroundPaint); 988 backgroundRectTop = sectionTop; 989 } 990 currentLeft = ownLeft; 991 currentRight = ownRight; 992 lastSectionBottom = 993 section.getCurrentBounds().bottom + animationYOffset; 994 first = false; 995 } 996 canvas.drawRoundRect(currentLeft, 997 backgroundRectTop, 998 currentRight, 999 lastSectionBottom, 1000 mCornerRadius, mCornerRadius, mBackgroundPaint); 1001 } 1002 1003 private void drawHeadsUpBackground(Canvas canvas) { 1004 int left = mSidePaddings; 1005 int right = getWidth() - mSidePaddings; 1006 1007 float top = getHeight(); 1008 float bottom = 0; 1009 int childCount = getChildCount(); 1010 for (int i = 0; i < childCount; i++) { 1011 View child = getChildAt(i); 1012 if (child.getVisibility() != View.GONE 1013 && child instanceof ExpandableNotificationRow) { 1014 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 1015 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0 1016 && row.getProvider().shouldShowGutsOnSnapOpen()) { 1017 top = Math.min(top, row.getTranslationY()); 1018 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight()); 1019 } 1020 } 1021 } 1022 1023 if (top < bottom) { 1024 canvas.drawRoundRect( 1025 left, top, right, bottom, 1026 mCornerRadius, mCornerRadius, mBackgroundPaint); 1027 } 1028 } 1029 1030 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1031 private void updateBackgroundDimming() { 1032 // No need to update the background color if it's not being drawn. 1033 if (!mShouldDrawNotificationBackground) { 1034 return; 1035 } 1036 1037 // Interpolate between semi-transparent notification panel background color 1038 // and white AOD separator. 1039 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */, 1040 mLinearHideAmount); 1041 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation); 1042 1043 if (mCachedBackgroundColor != color) { 1044 mCachedBackgroundColor = color; 1045 mBackgroundPaint.setColor(color); 1046 invalidate(); 1047 } 1048 } 1049 1050 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1051 private void initView(Context context) { 1052 mScroller = new OverScroller(getContext()); 1053 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 1054 setClipChildren(false); 1055 final ViewConfiguration configuration = ViewConfiguration.get(context); 1056 mTouchSlop = configuration.getScaledTouchSlop(); 1057 mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); 1058 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 1059 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 1060 mOverflingDistance = configuration.getScaledOverflingDistance(); 1061 1062 Resources res = context.getResources(); 1063 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); 1064 mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); 1065 mStackScrollAlgorithm.initView(context); 1066 mAmbientState.reload(context); 1067 mPaddingBetweenElements = Math.max(1, 1068 res.getDimensionPixelSize(R.dimen.notification_divider_height)); 1069 mIncreasedPaddingBetweenElements = 1070 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased); 1071 mMinTopOverScrollToEscape = res.getDimensionPixelSize( 1072 R.dimen.min_top_overscroll_to_qs); 1073 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); 1074 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); 1075 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); 1076 mMinInteractionHeight = res.getDimensionPixelSize( 1077 R.dimen.notification_min_interaction_height); 1078 mCornerRadius = res.getDimensionPixelSize( 1079 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); 1080 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( 1081 R.dimen.heads_up_status_bar_padding); 1082 } 1083 1084 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1085 private void notifyHeightChangeListener(ExpandableView view) { 1086 notifyHeightChangeListener(view, false /* needsAnimation */); 1087 } 1088 1089 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1090 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) { 1091 if (mOnHeightChangedListener != null) { 1092 mOnHeightChangedListener.onHeightChanged(view, needsAnimation); 1093 } 1094 } 1095 1096 public boolean isPulseExpanding() { 1097 return mAmbientState.isPulseExpanding(); 1098 } 1099 1100 @Override 1101 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1102 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1103 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1104 1105 int width = MeasureSpec.getSize(widthMeasureSpec); 1106 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2, 1107 MeasureSpec.getMode(widthMeasureSpec)); 1108 // Don't constrain the height of the children so we know how big they'd like to be 1109 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), 1110 MeasureSpec.UNSPECIFIED); 1111 1112 // We need to measure all children even the GONE ones, such that the heights are calculated 1113 // correctly as they are used to calculate how many we can fit on the screen. 1114 final int size = getChildCount(); 1115 for (int i = 0; i < size; i++) { 1116 measureChild(getChildAt(i), childWidthSpec, childHeightSpec); 1117 } 1118 } 1119 1120 @Override 1121 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1122 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1123 // we layout all our children centered on the top 1124 float centerX = getWidth() / 2.0f; 1125 for (int i = 0; i < getChildCount(); i++) { 1126 View child = getChildAt(i); 1127 // We need to layout all children even the GONE ones, such that the heights are 1128 // calculated correctly as they are used to calculate how many we can fit on the screen 1129 float width = child.getMeasuredWidth(); 1130 float height = child.getMeasuredHeight(); 1131 child.layout((int) (centerX - width / 2.0f), 1132 0, 1133 (int) (centerX + width / 2.0f), 1134 (int) height); 1135 } 1136 setMaxLayoutHeight(getHeight()); 1137 updateContentHeight(); 1138 clampScrollPosition(); 1139 requestChildrenUpdate(); 1140 updateFirstAndLastBackgroundViews(); 1141 updateAlgorithmLayoutMinHeight(); 1142 updateOwnTranslationZ(); 1143 } 1144 1145 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1146 private void requestAnimationOnViewResize(ExpandableNotificationRow row) { 1147 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) { 1148 mNeedViewResizeAnimation = true; 1149 mNeedsAnimation = true; 1150 } 1151 } 1152 1153 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1154 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) { 1155 mAmbientState.setSpeedBumpIndex(newIndex); 1156 mNoAmbient = noAmbient; 1157 } 1158 1159 @Override 1160 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1161 public void setChildLocationsChangedListener( 1162 NotificationLogger.OnChildLocationsChangedListener listener) { 1163 mListener = listener; 1164 } 1165 1166 @Override 1167 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1168 public boolean isInVisibleLocation(NotificationEntry entry) { 1169 ExpandableNotificationRow row = entry.getRow(); 1170 ExpandableViewState childViewState = row.getViewState(); 1171 1172 if (childViewState == null) { 1173 return false; 1174 } 1175 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) { 1176 return false; 1177 } 1178 if (row.getVisibility() != View.VISIBLE) { 1179 return false; 1180 } 1181 return true; 1182 } 1183 1184 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1185 private void setMaxLayoutHeight(int maxLayoutHeight) { 1186 mMaxLayoutHeight = maxLayoutHeight; 1187 mShelf.setMaxLayoutHeight(maxLayoutHeight); 1188 updateAlgorithmHeightAndPadding(); 1189 } 1190 1191 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1192 private void updateAlgorithmHeightAndPadding() { 1193 mAmbientState.setLayoutHeight(getLayoutHeight()); 1194 updateAlgorithmLayoutMinHeight(); 1195 mAmbientState.setTopPadding(mTopPadding); 1196 } 1197 1198 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1199 private void updateAlgorithmLayoutMinHeight() { 1200 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition() 1201 ? getLayoutMinHeight() : 0); 1202 } 1203 1204 /** 1205 * Updates the children views according to the stack scroll algorithm. Call this whenever 1206 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 1207 */ 1208 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1209 private void updateChildren() { 1210 updateScrollStateForAddedChildren(); 1211 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() 1212 ? 0 1213 : mScroller.getCurrVelocity()); 1214 if (ANCHOR_SCROLLING) { 1215 mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView)); 1216 mAmbientState.setAnchorViewY(mScrollAnchorViewY); 1217 } else { 1218 mAmbientState.setScrollY(mOwnScrollY); 1219 } 1220 mStackScrollAlgorithm.resetViewStates(mAmbientState); 1221 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 1222 applyCurrentState(); 1223 } else { 1224 startAnimationToState(); 1225 } 1226 } 1227 1228 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1229 private void onPreDrawDuringAnimation() { 1230 mShelf.updateAppearance(); 1231 updateClippingToTopRoundedCorner(); 1232 if (!mNeedsAnimation && !mChildrenUpdateRequested) { 1233 updateBackground(); 1234 } 1235 } 1236 1237 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1238 private void updateClippingToTopRoundedCorner() { 1239 Float clipStart = (float) mTopPadding 1240 + mStackTranslation 1241 + mAmbientState.getExpandAnimationTopChange(); 1242 Float clipEnd = clipStart + mCornerRadius; 1243 boolean first = true; 1244 for (int i = 0; i < getChildCount(); i++) { 1245 ExpandableView child = (ExpandableView) getChildAt(i); 1246 if (child.getVisibility() == GONE) { 1247 continue; 1248 } 1249 float start = child.getTranslationY(); 1250 float end = start + child.getActualHeight(); 1251 boolean clip = clipStart > start && clipStart < end 1252 || clipEnd >= start && clipEnd <= end; 1253 clip &= !(first && isScrolledToTop()); 1254 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) 1255 : ExpandableView.NO_ROUNDNESS); 1256 first = false; 1257 } 1258 } 1259 1260 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1261 private void updateScrollStateForAddedChildren() { 1262 if (mChildrenToAddAnimated.isEmpty()) { 1263 return; 1264 } 1265 if (!ANCHOR_SCROLLING) { 1266 for (int i = 0; i < getChildCount(); i++) { 1267 ExpandableView child = (ExpandableView) getChildAt(i); 1268 if (mChildrenToAddAnimated.contains(child)) { 1269 int startingPosition = getPositionInLinearLayout(child); 1270 float increasedPaddingAmount = child.getIncreasedPaddingAmount(); 1271 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements 1272 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements; 1273 int childHeight = getIntrinsicHeight(child) + padding; 1274 if (startingPosition < mOwnScrollY) { 1275 // This child starts off screen, so let's keep it offscreen to keep the 1276 // others visible 1277 1278 setOwnScrollY(mOwnScrollY + childHeight); 1279 } 1280 } 1281 } 1282 } 1283 clampScrollPosition(); 1284 } 1285 1286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1287 private void updateForcedScroll() { 1288 if (mForcedScroll != null && (!mForcedScroll.hasFocus() 1289 || !mForcedScroll.isAttachedToWindow())) { 1290 mForcedScroll = null; 1291 } 1292 if (mForcedScroll != null) { 1293 ExpandableView expandableView = (ExpandableView) mForcedScroll; 1294 int positionInLinearLayout = getPositionInLinearLayout(expandableView); 1295 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1296 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1297 1298 if (ANCHOR_SCROLLING) { 1299 // TODO 1300 } else { 1301 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); 1302 1303 // Only apply the scroll if we're scrolling the view upwards, or the view is so 1304 // far up that it is not visible anymore. 1305 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1306 setOwnScrollY(targetScroll); 1307 } 1308 } 1309 } 1310 } 1311 1312 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1313 private void requestChildrenUpdate() { 1314 if (!mChildrenUpdateRequested) { 1315 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 1316 mChildrenUpdateRequested = true; 1317 invalidate(); 1318 } 1319 } 1320 1321 /** 1322 * Returns best effort count of visible notifications. 1323 */ 1324 public int getVisibleNotificationCount() { 1325 int count = 0; 1326 for (int i = 0; i < getChildCount(); i++) { 1327 final View child = getChildAt(i); 1328 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { 1329 count++; 1330 } 1331 } 1332 return count; 1333 } 1334 1335 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1336 private boolean isCurrentlyAnimating() { 1337 return mStateAnimator.isRunning(); 1338 } 1339 1340 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1341 private void clampScrollPosition() { 1342 if (ANCHOR_SCROLLING) { 1343 // TODO 1344 } else { 1345 int scrollRange = getScrollRange(); 1346 if (scrollRange < mOwnScrollY) { 1347 setOwnScrollY(scrollRange); 1348 } 1349 } 1350 } 1351 1352 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1353 public int getTopPadding() { 1354 return mTopPadding; 1355 } 1356 1357 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1358 private void setTopPadding(int topPadding, boolean animate) { 1359 if (mTopPadding != topPadding) { 1360 mTopPadding = topPadding; 1361 updateAlgorithmHeightAndPadding(); 1362 updateContentHeight(); 1363 if (animate && mAnimationsEnabled && mIsExpanded) { 1364 mTopPaddingNeedsAnimation = true; 1365 mNeedsAnimation = true; 1366 } 1367 requestChildrenUpdate(); 1368 notifyHeightChangeListener(null, animate); 1369 } 1370 } 1371 1372 /** 1373 * Update the height of the panel. 1374 * 1375 * @param height the expanded height of the panel 1376 */ 1377 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1378 public void setExpandedHeight(float height) { 1379 mExpandedHeight = height; 1380 setIsExpanded(height > 0); 1381 int minExpansionHeight = getMinExpansionHeight(); 1382 if (height < minExpansionHeight) { 1383 mClipRect.left = 0; 1384 mClipRect.right = getWidth(); 1385 mClipRect.top = 0; 1386 mClipRect.bottom = (int) height; 1387 height = minExpansionHeight; 1388 setRequestedClipBounds(mClipRect); 1389 } else { 1390 setRequestedClipBounds(null); 1391 } 1392 int stackHeight; 1393 float translationY; 1394 float appearEndPosition = getAppearEndPosition(); 1395 float appearStartPosition = getAppearStartPosition(); 1396 float appearFraction = 1.0f; 1397 boolean appearing = height < appearEndPosition; 1398 mAmbientState.setAppearing(appearing); 1399 if (!appearing) { 1400 translationY = 0; 1401 if (mShouldShowShelfOnly) { 1402 stackHeight = mTopPadding + mShelf.getIntrinsicHeight(); 1403 } else if (mQsExpanded) { 1404 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding; 1405 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight(); 1406 if (stackStartPosition <= stackEndPosition) { 1407 stackHeight = stackEndPosition; 1408 } else { 1409 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition, 1410 stackEndPosition, mQsExpansionFraction); 1411 } 1412 } else { 1413 stackHeight = (int) height; 1414 } 1415 } else { 1416 appearFraction = calculateAppearFraction(height); 1417 if (appearFraction >= 0) { 1418 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, 1419 appearFraction); 1420 } else { 1421 // This may happen when pushing up a heads up. We linearly push it up from the 1422 // start 1423 translationY = height - appearStartPosition + getExpandTranslationStart(); 1424 } 1425 stackHeight = (int) (height - translationY); 1426 if (isHeadsUpTransition()) { 1427 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); 1428 } 1429 } 1430 mAmbientState.setAppearFraction(appearFraction); 1431 if (stackHeight != mCurrentStackHeight) { 1432 mCurrentStackHeight = stackHeight; 1433 updateAlgorithmHeightAndPadding(); 1434 requestChildrenUpdate(); 1435 } 1436 setStackTranslation(translationY); 1437 notifyAppearChangedListeners(); 1438 } 1439 1440 private void notifyAppearChangedListeners() { 1441 float appear; 1442 float expandAmount; 1443 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) { 1444 appear = calculateAppearFractionBypass(); 1445 expandAmount = getPulseHeight(); 1446 } else { 1447 appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight)); 1448 expandAmount = mExpandedHeight; 1449 } 1450 if (appear != mLastSentAppear || expandAmount != mLastSentExpandedHeight) { 1451 mLastSentAppear = appear; 1452 mLastSentExpandedHeight = expandAmount; 1453 for (int i = 0; i < mExpandedHeightListeners.size(); i++) { 1454 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i); 1455 listener.accept(expandAmount, appear); 1456 } 1457 } 1458 } 1459 1460 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1461 private void setRequestedClipBounds(Rect clipRect) { 1462 mRequestedClipBounds = clipRect; 1463 updateClipping(); 1464 } 1465 1466 /** 1467 * Return the height of the content ignoring the footer. 1468 */ 1469 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1470 public int getIntrinsicContentHeight() { 1471 return mIntrinsicContentHeight; 1472 } 1473 1474 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1475 public void updateClipping() { 1476 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode 1477 && !mHeadsUpAnimatingAway; 1478 boolean clipToOutline = false; 1479 if (mIsClipped != clipped) { 1480 mIsClipped = clipped; 1481 } 1482 1483 if (mAmbientState.isHiddenAtAll()) { 1484 clipToOutline = true; 1485 invalidateOutline(); 1486 if (isFullyHidden()) { 1487 setClipBounds(null); 1488 } 1489 } else if (clipped) { 1490 setClipBounds(mRequestedClipBounds); 1491 } else { 1492 setClipBounds(null); 1493 } 1494 1495 setClipToOutline(clipToOutline); 1496 } 1497 1498 /** 1499 * @return The translation at the beginning when expanding. 1500 * Measured relative to the resting position. 1501 */ 1502 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1503 private float getExpandTranslationStart() { 1504 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); 1505 } 1506 1507 /** 1508 * @return the position from where the appear transition starts when expanding. 1509 * Measured in absolute height. 1510 */ 1511 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1512 private float getAppearStartPosition() { 1513 if (isHeadsUpTransition()) { 1514 return mHeadsUpInset 1515 + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight(); 1516 } 1517 return getMinExpansionHeight(); 1518 } 1519 1520 /** 1521 * @return the height of the top heads up notification when pinned. This is different from the 1522 * intrinsic height, which also includes whether the notification is system expanded and 1523 * is mainly used when dragging down from a heads up notification. 1524 */ 1525 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1526 private int getTopHeadsUpPinnedHeight() { 1527 NotificationEntry topEntry = mHeadsUpManager.getTopEntry(); 1528 if (topEntry == null) { 1529 return 0; 1530 } 1531 ExpandableNotificationRow row = topEntry.getRow(); 1532 if (row.isChildInGroup()) { 1533 final NotificationEntry groupSummary = 1534 mGroupManager.getGroupSummary(row.getEntry().getSbn()); 1535 if (groupSummary != null) { 1536 row = groupSummary.getRow(); 1537 } 1538 } 1539 return row.getPinnedHeadsUpHeight(); 1540 } 1541 1542 /** 1543 * @return the position from where the appear transition ends when expanding. 1544 * Measured in absolute height. 1545 */ 1546 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1547 private float getAppearEndPosition() { 1548 int appearPosition = 0; 1549 int visibleNotifCount = getVisibleNotificationCount(); 1550 if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { 1551 if (isHeadsUpTransition() 1552 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) { 1553 if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) { 1554 appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; 1555 } 1556 appearPosition += getTopHeadsUpPinnedHeight() 1557 + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); 1558 } else if (mShelf.getVisibility() != GONE) { 1559 appearPosition += mShelf.getIntrinsicHeight(); 1560 } 1561 } else { 1562 appearPosition = mEmptyShadeView.getHeight(); 1563 } 1564 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); 1565 } 1566 1567 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1568 private boolean isHeadsUpTransition() { 1569 return mAmbientState.getTrackedHeadsUpRow() != null; 1570 } 1571 1572 /** 1573 * @param height the height of the panel 1574 * @return the fraction of the appear animation that has been performed 1575 */ 1576 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1577 public float calculateAppearFraction(float height) { 1578 float appearEndPosition = getAppearEndPosition(); 1579 float appearStartPosition = getAppearStartPosition(); 1580 return (height - appearStartPosition) 1581 / (appearEndPosition - appearStartPosition); 1582 } 1583 1584 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1585 public float getStackTranslation() { 1586 return mStackTranslation; 1587 } 1588 1589 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1590 private void setStackTranslation(float stackTranslation) { 1591 if (stackTranslation != mStackTranslation) { 1592 mStackTranslation = stackTranslation; 1593 mAmbientState.setStackTranslation(stackTranslation); 1594 requestChildrenUpdate(); 1595 } 1596 } 1597 1598 /** 1599 * Get the current height of the view. This is at most the msize of the view given by a the 1600 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 1601 * 1602 * @return either the layout height or the externally defined height, whichever is smaller 1603 */ 1604 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1605 private int getLayoutHeight() { 1606 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 1607 } 1608 1609 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1610 public int getFirstItemMinHeight() { 1611 final ExpandableView firstChild = getFirstChildNotGone(); 1612 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; 1613 } 1614 1615 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1616 public void setQsContainer(ViewGroup qsContainer) { 1617 mQsContainer = qsContainer; 1618 } 1619 1620 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1621 public static boolean isPinnedHeadsUp(View v) { 1622 if (v instanceof ExpandableNotificationRow) { 1623 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1624 return row.isHeadsUp() && row.isPinned(); 1625 } 1626 return false; 1627 } 1628 1629 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1630 private boolean isHeadsUp(View v) { 1631 if (v instanceof ExpandableNotificationRow) { 1632 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1633 return row.isHeadsUp(); 1634 } 1635 return false; 1636 } 1637 1638 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1639 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { 1640 getLocationOnScreen(mTempInt2); 1641 float localTouchY = touchY - mTempInt2[1]; 1642 1643 ExpandableView closestChild = null; 1644 float minDist = Float.MAX_VALUE; 1645 1646 // find the view closest to the location, accounting for GONE views 1647 final int count = getChildCount(); 1648 for (int childIdx = 0; childIdx < count; childIdx++) { 1649 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 1650 if (slidingChild.getVisibility() == GONE 1651 || slidingChild instanceof StackScrollerDecorView) { 1652 continue; 1653 } 1654 float childTop = slidingChild.getTranslationY(); 1655 float top = childTop + slidingChild.getClipTopAmount(); 1656 float bottom = childTop + slidingChild.getActualHeight() 1657 - slidingChild.getClipBottomAmount(); 1658 1659 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY)); 1660 if (dist < minDist) { 1661 closestChild = slidingChild; 1662 minDist = dist; 1663 } 1664 } 1665 return closestChild; 1666 } 1667 1668 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1669 private ExpandableView getChildAtPosition(float touchX, float touchY) { 1670 return getChildAtPosition( 1671 touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */); 1672 } 1673 1674 /** 1675 * Get the child at a certain screen location. 1676 * 1677 * @param touchX the x coordinate 1678 * @param touchY the y coordinate 1679 * @param requireMinHeight Whether a minimum height is required for a child to be returned. 1680 * @param ignoreDecors Whether decors can be returned 1681 * @return the child at the given location. 1682 */ 1683 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1684 private ExpandableView getChildAtPosition(float touchX, float touchY, 1685 boolean requireMinHeight, boolean ignoreDecors) { 1686 // find the view under the pointer, accounting for GONE views 1687 final int count = getChildCount(); 1688 for (int childIdx = 0; childIdx < count; childIdx++) { 1689 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 1690 if (slidingChild.getVisibility() != VISIBLE 1691 || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) { 1692 continue; 1693 } 1694 float childTop = slidingChild.getTranslationY(); 1695 float top = childTop + slidingChild.getClipTopAmount(); 1696 float bottom = childTop + slidingChild.getActualHeight() 1697 - slidingChild.getClipBottomAmount(); 1698 1699 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and 1700 // camera affordance). 1701 int left = 0; 1702 int right = getWidth(); 1703 1704 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight) 1705 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 1706 if (slidingChild instanceof ExpandableNotificationRow) { 1707 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; 1708 NotificationEntry entry = row.getEntry(); 1709 if (!mIsExpanded && row.isHeadsUp() && row.isPinned() 1710 && mHeadsUpManager.getTopEntry().getRow() != row 1711 && mGroupManager.getGroupSummary( 1712 mHeadsUpManager.getTopEntry().getSbn()) 1713 != entry) { 1714 continue; 1715 } 1716 return row.getViewAtPosition(touchY - childTop); 1717 } 1718 return slidingChild; 1719 } 1720 } 1721 return null; 1722 } 1723 1724 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 1725 getLocationOnScreen(mTempInt2); 1726 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); 1727 } 1728 1729 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1730 public void setScrollingEnabled(boolean enable) { 1731 mScrollingEnabled = enable; 1732 } 1733 1734 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1735 public void lockScrollTo(View v) { 1736 if (mForcedScroll == v) { 1737 return; 1738 } 1739 mForcedScroll = v; 1740 scrollTo(v); 1741 } 1742 1743 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1744 public boolean scrollTo(View v) { 1745 ExpandableView expandableView = (ExpandableView) v; 1746 if (ANCHOR_SCROLLING) { 1747 // TODO 1748 } else { 1749 int positionInLinearLayout = getPositionInLinearLayout(v); 1750 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1751 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1752 1753 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 1754 // that it is not visible anymore. 1755 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1756 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); 1757 mDontReportNextOverScroll = true; 1758 animateScroll(); 1759 return true; 1760 } 1761 } 1762 return false; 1763 } 1764 1765 /** 1766 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of 1767 * the IME. 1768 */ 1769 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1770 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { 1771 return positionInLinearLayout + v.getIntrinsicHeight() + 1772 getImeInset() - getHeight() 1773 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding()); 1774 } 1775 1776 @Override 1777 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1778 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1779 mBottomInset = insets.getSystemWindowInsetBottom(); 1780 1781 mWaterfallTopInset = 0; 1782 final DisplayCutout cutout = insets.getDisplayCutout(); 1783 if (cutout != null) { 1784 mWaterfallTopInset = cutout.getWaterfallInsets().top; 1785 } 1786 1787 if (ANCHOR_SCROLLING) { 1788 // TODO 1789 } else { 1790 int range = getScrollRange(); 1791 if (mOwnScrollY > range) { 1792 // HACK: We're repeatedly getting staggered insets here while the IME is 1793 // animating away. To work around that we'll wait until things have settled. 1794 removeCallbacks(mReclamp); 1795 postDelayed(mReclamp, 50); 1796 } else if (mForcedScroll != null) { 1797 // The scroll was requested before we got the actual inset - in case we need 1798 // to scroll up some more do so now. 1799 scrollTo(mForcedScroll); 1800 } 1801 } 1802 return insets; 1803 } 1804 1805 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1806 private Runnable mReclamp = new Runnable() { 1807 @Override 1808 public void run() { 1809 if (ANCHOR_SCROLLING) { 1810 // TODO 1811 } else { 1812 int range = getScrollRange(); 1813 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); 1814 } 1815 mDontReportNextOverScroll = true; 1816 mDontClampNextScroll = true; 1817 animateScroll(); 1818 } 1819 }; 1820 1821 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1822 public void setExpandingEnabled(boolean enable) { 1823 mExpandHelper.setEnabled(enable); 1824 } 1825 1826 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1827 private boolean isScrollingEnabled() { 1828 return mScrollingEnabled; 1829 } 1830 1831 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1832 private boolean onKeyguard() { 1833 return mStatusBarState == StatusBarState.KEYGUARD; 1834 } 1835 1836 @Override 1837 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1838 protected void onConfigurationChanged(Configuration newConfig) { 1839 super.onConfigurationChanged(newConfig); 1840 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); 1841 float densityScale = getResources().getDisplayMetrics().density; 1842 mSwipeHelper.setDensityScale(densityScale); 1843 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 1844 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 1845 initView(getContext()); 1846 } 1847 1848 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1849 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { 1850 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, 1851 true /* isDismissAll */); 1852 } 1853 1854 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1855 private void snapViewIfNeeded(NotificationEntry entry) { 1856 ExpandableNotificationRow child = entry.getRow(); 1857 boolean animate = mIsExpanded || isPinnedHeadsUp(child); 1858 // If the child is showing the notification menu snap to that 1859 if (child.getProvider() != null) { 1860 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0; 1861 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft); 1862 } 1863 } 1864 1865 @Override 1866 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1867 public ViewGroup getViewParentForNotification(NotificationEntry entry) { 1868 return this; 1869 } 1870 1871 /** 1872 * Perform a scroll upwards and adapt the overscroll amounts accordingly 1873 * 1874 * @param deltaY The amount to scroll upwards, has to be positive. 1875 * @return The amount of scrolling to be performed by the scroller, 1876 * not handled by the overScroll amount. 1877 */ 1878 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1879 private float overScrollUp(int deltaY, int range) { 1880 deltaY = Math.max(deltaY, 0); 1881 float currentTopAmount = getCurrentOverScrollAmount(true); 1882 float newTopAmount = currentTopAmount - deltaY; 1883 if (currentTopAmount > 0) { 1884 setOverScrollAmount(newTopAmount, true /* onTop */, 1885 false /* animate */); 1886 } 1887 // Top overScroll might not grab all scrolling motion, 1888 // we have to scroll as well. 1889 if (ANCHOR_SCROLLING) { 1890 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 1891 // TODO: once we're recycling this will need to check the adapter position of the child 1892 ExpandableView lastRow = getLastRowNotGone(); 1893 if (lastRow != null && !lastRow.isInShelf()) { 1894 float distanceToMax = Math.max(0, getMaxPositiveScrollAmount()); 1895 if (scrollAmount > distanceToMax) { 1896 float currentBottomPixels = getCurrentOverScrolledPixels(false); 1897 // We overScroll on the bottom 1898 setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax), 1899 false /* onTop */, 1900 false /* animate */); 1901 mScrollAnchorViewY -= distanceToMax; 1902 scrollAmount = 0f; 1903 } 1904 } 1905 return scrollAmount; 1906 } else { 1907 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 1908 float newScrollY = mOwnScrollY + scrollAmount; 1909 if (newScrollY > range) { 1910 if (!mExpandedInThisMotion) { 1911 float currentBottomPixels = getCurrentOverScrolledPixels(false); 1912 // We overScroll on the bottom 1913 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 1914 false /* onTop */, 1915 false /* animate */); 1916 } 1917 setOwnScrollY(range); 1918 scrollAmount = 0.0f; 1919 } 1920 return scrollAmount; 1921 } 1922 } 1923 1924 /** 1925 * Perform a scroll downward and adapt the overscroll amounts accordingly 1926 * 1927 * @param deltaY The amount to scroll downwards, has to be negative. 1928 * @return The amount of scrolling to be performed by the scroller, 1929 * not handled by the overScroll amount. 1930 */ 1931 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1932 private float overScrollDown(int deltaY) { 1933 deltaY = Math.min(deltaY, 0); 1934 float currentBottomAmount = getCurrentOverScrollAmount(false); 1935 float newBottomAmount = currentBottomAmount + deltaY; 1936 if (currentBottomAmount > 0) { 1937 setOverScrollAmount(newBottomAmount, false /* onTop */, 1938 false /* animate */); 1939 } 1940 // Bottom overScroll might not grab all scrolling motion, 1941 // we have to scroll as well. 1942 if (ANCHOR_SCROLLING) { 1943 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 1944 // TODO: once we're recycling this will need to check the adapter position of the child 1945 ExpandableView firstChild = getFirstChildNotGone(); 1946 float top = firstChild.getTranslationY(); 1947 float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY; 1948 if (distanceToTop < -scrollAmount) { 1949 float currentTopPixels = getCurrentOverScrolledPixels(true); 1950 // We overScroll on the top 1951 setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop), 1952 true /* onTop */, 1953 false /* animate */); 1954 mScrollAnchorView = firstChild; 1955 mScrollAnchorViewY = 0; 1956 scrollAmount = 0f; 1957 } 1958 return scrollAmount; 1959 } else { 1960 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 1961 float newScrollY = mOwnScrollY + scrollAmount; 1962 if (newScrollY < 0) { 1963 float currentTopPixels = getCurrentOverScrolledPixels(true); 1964 // We overScroll on the top 1965 setOverScrolledPixels(currentTopPixels - newScrollY, 1966 true /* onTop */, 1967 false /* animate */); 1968 setOwnScrollY(0); 1969 scrollAmount = 0.0f; 1970 } 1971 return scrollAmount; 1972 } 1973 } 1974 1975 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1976 private void initVelocityTrackerIfNotExists() { 1977 if (mVelocityTracker == null) { 1978 mVelocityTracker = VelocityTracker.obtain(); 1979 } 1980 } 1981 1982 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1983 private void recycleVelocityTracker() { 1984 if (mVelocityTracker != null) { 1985 mVelocityTracker.recycle(); 1986 mVelocityTracker = null; 1987 } 1988 } 1989 1990 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1991 private void initOrResetVelocityTracker() { 1992 if (mVelocityTracker == null) { 1993 mVelocityTracker = VelocityTracker.obtain(); 1994 } else { 1995 mVelocityTracker.clear(); 1996 } 1997 } 1998 1999 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2000 public void setFinishScrollingCallback(Runnable runnable) { 2001 mFinishScrollingCallback = runnable; 2002 } 2003 2004 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2005 private void animateScroll() { 2006 if (mScroller.computeScrollOffset()) { 2007 if (ANCHOR_SCROLLING) { 2008 int oldY = mLastScrollerY; 2009 int y = mScroller.getCurrY(); 2010 int deltaY = y - oldY; 2011 if (deltaY != 0) { 2012 int maxNegativeScrollAmount = getMaxNegativeScrollAmount(); 2013 int maxPositiveScrollAmount = getMaxPositiveScrollAmount(); 2014 if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount) 2015 || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) { 2016 // This frame takes us into overscroll, so set the max overscroll based on 2017 // the current velocity 2018 setMaxOverScrollFromCurrentVelocity(); 2019 } 2020 customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll); 2021 mLastScrollerY = y; 2022 } 2023 } else { 2024 int oldY = mOwnScrollY; 2025 int y = mScroller.getCurrY(); 2026 2027 if (oldY != y) { 2028 int range = getScrollRange(); 2029 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 2030 // This frame takes us into overscroll, so set the max overscroll based on 2031 // the current velocity 2032 setMaxOverScrollFromCurrentVelocity(); 2033 } 2034 2035 if (mDontClampNextScroll) { 2036 range = Math.max(range, oldY); 2037 } 2038 customOverScrollBy(y - oldY, oldY, range, 2039 (int) (mMaxOverScroll)); 2040 } 2041 } 2042 2043 postOnAnimation(mReflingAndAnimateScroll); 2044 } else { 2045 mDontClampNextScroll = false; 2046 if (mFinishScrollingCallback != null) { 2047 mFinishScrollingCallback.run(); 2048 } 2049 } 2050 } 2051 2052 private void setMaxOverScrollFromCurrentVelocity() { 2053 float currVelocity = mScroller.getCurrVelocity(); 2054 if (currVelocity >= mMinimumVelocity) { 2055 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 2056 } 2057 } 2058 2059 /** 2060 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta 2061 * would cause us to exceed the provided maximum overscroll, springs back instead. 2062 * 2063 * This method performs the determination of whether we're exceeding the overscroll and clamps 2064 * the scroll amount if so. The actual scrolling/overscrolling happens in 2065 * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or 2066 * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling). 2067 * 2068 * @param deltaY The (signed) number of pixels to scroll. 2069 * @param scrollY The current scroll position (absolute scrolling only). 2070 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only). 2071 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by. 2072 */ 2073 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2074 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) { 2075 if (ANCHOR_SCROLLING) { 2076 boolean clampedY = false; 2077 if (deltaY < 0) { 2078 int maxScrollAmount = getMaxNegativeScrollAmount(); 2079 if (maxScrollAmount > Integer.MIN_VALUE) { 2080 maxScrollAmount -= maxOverScrollY; 2081 if (deltaY < maxScrollAmount) { 2082 deltaY = maxScrollAmount; 2083 clampedY = true; 2084 } 2085 } 2086 } else { 2087 int maxScrollAmount = getMaxPositiveScrollAmount(); 2088 if (maxScrollAmount < Integer.MAX_VALUE) { 2089 maxScrollAmount += maxOverScrollY; 2090 if (deltaY > maxScrollAmount) { 2091 deltaY = maxScrollAmount; 2092 clampedY = true; 2093 } 2094 } 2095 } 2096 onCustomOverScrolledBy(deltaY, clampedY); 2097 } else { 2098 int newScrollY = scrollY + deltaY; 2099 final int top = -maxOverScrollY; 2100 final int bottom = maxOverScrollY + scrollRangeY; 2101 2102 boolean clampedY = false; 2103 if (newScrollY > bottom) { 2104 newScrollY = bottom; 2105 clampedY = true; 2106 } else if (newScrollY < top) { 2107 newScrollY = top; 2108 clampedY = true; 2109 } 2110 2111 onCustomOverScrolled(newScrollY, clampedY); 2112 } 2113 } 2114 2115 /** 2116 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 2117 * overscroll effect based on numPixels. By default this will also cancel animations on the 2118 * same overScroll edge. 2119 * 2120 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 2121 * the rubber-banding logic. 2122 * @param onTop Should the effect be applied on top of the scroller. 2123 * @param animate Should an animation be performed. 2124 */ 2125 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2126 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 2127 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 2128 } 2129 2130 /** 2131 * Set the effective overScroll amount which will be directly reflected in the layout. 2132 * By default this will also cancel animations on the same overScroll edge. 2133 * 2134 * @param amount The amount to overScroll by. 2135 * @param onTop Should the effect be applied on top of the scroller. 2136 * @param animate Should an animation be performed. 2137 */ 2138 2139 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2140 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 2141 setOverScrollAmount(amount, onTop, animate, true); 2142 } 2143 2144 /** 2145 * Set the effective overScroll amount which will be directly reflected in the layout. 2146 * 2147 * @param amount The amount to overScroll by. 2148 * @param onTop Should the effect be applied on top of the scroller. 2149 * @param animate Should an animation be performed. 2150 * @param cancelAnimators Should running animations be cancelled. 2151 */ 2152 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2153 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 2154 boolean cancelAnimators) { 2155 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 2156 } 2157 2158 /** 2159 * Set the effective overScroll amount which will be directly reflected in the layout. 2160 * 2161 * @param amount The amount to overScroll by. 2162 * @param onTop Should the effect be applied on top of the scroller. 2163 * @param animate Should an animation be performed. 2164 * @param cancelAnimators Should running animations be cancelled. 2165 * @param isRubberbanded The value which will be passed to 2166 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 2167 */ 2168 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2169 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 2170 boolean cancelAnimators, boolean isRubberbanded) { 2171 if (cancelAnimators) { 2172 mStateAnimator.cancelOverScrollAnimators(onTop); 2173 } 2174 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 2175 } 2176 2177 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2178 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 2179 boolean isRubberbanded) { 2180 amount = Math.max(0, amount); 2181 if (animate) { 2182 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 2183 } else { 2184 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 2185 mAmbientState.setOverScrollAmount(amount, onTop); 2186 if (onTop) { 2187 notifyOverscrollTopListener(amount, isRubberbanded); 2188 } 2189 requestChildrenUpdate(); 2190 } 2191 } 2192 2193 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2194 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 2195 mExpandHelper.onlyObserveMovements(amount > 1.0f); 2196 if (mDontReportNextOverScroll) { 2197 mDontReportNextOverScroll = false; 2198 return; 2199 } 2200 if (mOverscrollTopChangedListener != null) { 2201 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 2202 } 2203 } 2204 2205 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2206 public void setOverscrollTopChangedListener( 2207 OnOverscrollTopChangedListener overscrollTopChangedListener) { 2208 mOverscrollTopChangedListener = overscrollTopChangedListener; 2209 } 2210 2211 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2212 public float getCurrentOverScrollAmount(boolean top) { 2213 return mAmbientState.getOverScrollAmount(top); 2214 } 2215 2216 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2217 public float getCurrentOverScrolledPixels(boolean top) { 2218 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels; 2219 } 2220 2221 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2222 private void setOverScrolledPixels(float amount, boolean onTop) { 2223 if (onTop) { 2224 mOverScrolledTopPixels = amount; 2225 } else { 2226 mOverScrolledBottomPixels = amount; 2227 } 2228 } 2229 2230 /** 2231 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta 2232 * would cause us to exceed the provided maximum overscroll, springs back instead. 2233 * 2234 * @param deltaY The (signed) number of pixels to scroll. 2235 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached 2236 * the overscroll limit. 2237 */ 2238 private void onCustomOverScrolledBy(int deltaY, boolean clampedY) { 2239 assert ANCHOR_SCROLLING; 2240 mScrollAnchorViewY -= deltaY; 2241 // Treat animating scrolls differently; see #computeScroll() for why. 2242 if (!mScroller.isFinished()) { 2243 if (clampedY) { 2244 springBack(); 2245 } else { 2246 float overScrollTop = getCurrentOverScrollAmount(true /* top */); 2247 if (isScrolledToTop() && mScrollAnchorViewY > 0) { 2248 notifyOverscrollTopListener(mScrollAnchorViewY, 2249 isRubberbanded(true /* onTop */)); 2250 } else { 2251 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */)); 2252 } 2253 } 2254 } 2255 updateScrollAnchor(); 2256 updateOnScrollChange(); 2257 } 2258 2259 /** 2260 * Scrolls to the given position, overscrolling if needed. If called during a fling and the 2261 * position exceeds the provided maximum overscroll, springs back instead. 2262 * 2263 * @param scrollY The target scroll position. 2264 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached 2265 * the overscroll limit. 2266 */ 2267 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2268 private void onCustomOverScrolled(int scrollY, boolean clampedY) { 2269 assert !ANCHOR_SCROLLING; 2270 // Treat animating scrolls differently; see #computeScroll() for why. 2271 if (!mScroller.isFinished()) { 2272 setOwnScrollY(scrollY); 2273 if (clampedY) { 2274 springBack(); 2275 } else { 2276 float overScrollTop = getCurrentOverScrollAmount(true); 2277 if (mOwnScrollY < 0) { 2278 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 2279 } else { 2280 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 2281 } 2282 } 2283 } else { 2284 setOwnScrollY(scrollY); 2285 } 2286 } 2287 2288 /** 2289 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the 2290 * overscroll amount back to zero. 2291 */ 2292 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2293 private void springBack() { 2294 if (ANCHOR_SCROLLING) { 2295 boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0; 2296 int maxPositiveScrollAmount = getMaxPositiveScrollAmount(); 2297 boolean overscrolledBottom = maxPositiveScrollAmount < 0; 2298 if (overScrolledTop || overscrolledBottom) { 2299 float newAmount; 2300 if (overScrolledTop) { 2301 newAmount = mScrollAnchorViewY; 2302 mScrollAnchorViewY = 0; 2303 mDontReportNextOverScroll = true; 2304 } else { 2305 newAmount = -maxPositiveScrollAmount; 2306 mScrollAnchorViewY -= maxPositiveScrollAmount; 2307 } 2308 setOverScrollAmount(newAmount, overScrolledTop, false); 2309 setOverScrollAmount(0.0f, overScrolledTop, true); 2310 mScroller.forceFinished(true); 2311 } 2312 } else { 2313 int scrollRange = getScrollRange(); 2314 boolean overScrolledTop = mOwnScrollY <= 0; 2315 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 2316 if (overScrolledTop || overScrolledBottom) { 2317 boolean onTop; 2318 float newAmount; 2319 if (overScrolledTop) { 2320 onTop = true; 2321 newAmount = -mOwnScrollY; 2322 setOwnScrollY(0); 2323 mDontReportNextOverScroll = true; 2324 } else { 2325 onTop = false; 2326 newAmount = mOwnScrollY - scrollRange; 2327 setOwnScrollY(scrollRange); 2328 } 2329 setOverScrollAmount(newAmount, onTop, false); 2330 setOverScrollAmount(0.0f, onTop, true); 2331 mScroller.forceFinished(true); 2332 } 2333 } 2334 } 2335 2336 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2337 private int getScrollRange() { 2338 // In current design, it only use the top HUN to treat all of HUNs 2339 // although there are more than one HUNs 2340 int contentHeight = mContentHeight; 2341 if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) { 2342 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight(); 2343 } 2344 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight); 2345 int imeInset = getImeInset(); 2346 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset))); 2347 return scrollRange; 2348 } 2349 2350 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2351 private int getImeInset() { 2352 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight())); 2353 } 2354 2355 /** 2356 * @return the first child which has visibility unequal to GONE 2357 */ 2358 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2359 public ExpandableView getFirstChildNotGone() { 2360 int childCount = getChildCount(); 2361 for (int i = 0; i < childCount; i++) { 2362 View child = getChildAt(i); 2363 if (child.getVisibility() != View.GONE && child != mShelf) { 2364 return (ExpandableView) child; 2365 } 2366 } 2367 return null; 2368 } 2369 2370 /** 2371 * @return the child before the given view which has visibility unequal to GONE 2372 */ 2373 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2374 public ExpandableView getViewBeforeView(ExpandableView view) { 2375 ExpandableView previousView = null; 2376 int childCount = getChildCount(); 2377 for (int i = 0; i < childCount; i++) { 2378 View child = getChildAt(i); 2379 if (child == view) { 2380 return previousView; 2381 } 2382 if (child.getVisibility() != View.GONE) { 2383 previousView = (ExpandableView) child; 2384 } 2385 } 2386 return null; 2387 } 2388 2389 /** 2390 * @return The first child which has visibility unequal to GONE which is currently below the 2391 * given translationY or equal to it. 2392 */ 2393 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2394 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) { 2395 int childCount = getChildCount(); 2396 for (int i = 0; i < childCount; i++) { 2397 View child = getChildAt(i); 2398 if (child.getVisibility() == View.GONE) { 2399 continue; 2400 } 2401 float rowTranslation = child.getTranslationY(); 2402 if (rowTranslation >= translationY) { 2403 return child; 2404 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) { 2405 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2406 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { 2407 List<ExpandableNotificationRow> notificationChildren = 2408 row.getAttachedChildren(); 2409 for (int childIndex = 0; childIndex < notificationChildren.size(); 2410 childIndex++) { 2411 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex); 2412 if (rowChild.getTranslationY() + rowTranslation >= translationY) { 2413 return rowChild; 2414 } 2415 } 2416 } 2417 } 2418 } 2419 return null; 2420 } 2421 2422 /** 2423 * @return the last child which has visibility unequal to GONE 2424 */ 2425 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2426 public ExpandableView getLastChildNotGone() { 2427 int childCount = getChildCount(); 2428 for (int i = childCount - 1; i >= 0; i--) { 2429 View child = getChildAt(i); 2430 if (child.getVisibility() != View.GONE && child != mShelf) { 2431 return (ExpandableView) child; 2432 } 2433 } 2434 return null; 2435 } 2436 2437 private ExpandableNotificationRow getLastRowNotGone() { 2438 int childCount = getChildCount(); 2439 for (int i = childCount - 1; i >= 0; i--) { 2440 View child = getChildAt(i); 2441 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) { 2442 return (ExpandableNotificationRow) child; 2443 } 2444 } 2445 return null; 2446 } 2447 2448 /** 2449 * @return the number of children which have visibility unequal to GONE 2450 */ 2451 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2452 public int getNotGoneChildCount() { 2453 int childCount = getChildCount(); 2454 int count = 0; 2455 for (int i = 0; i < childCount; i++) { 2456 ExpandableView child = (ExpandableView) getChildAt(i); 2457 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) { 2458 count++; 2459 } 2460 } 2461 return count; 2462 } 2463 2464 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2465 private void updateContentHeight() { 2466 int height = 0; 2467 float previousPaddingRequest = mPaddingBetweenElements; 2468 float previousPaddingAmount = 0.0f; 2469 int numShownItems = 0; 2470 boolean finish = false; 2471 int maxDisplayedNotifications = mMaxDisplayedNotifications; 2472 ExpandableView previousView = null; 2473 for (int i = 0; i < getChildCount(); i++) { 2474 ExpandableView expandableView = (ExpandableView) getChildAt(i); 2475 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard(); 2476 if (expandableView.getVisibility() != View.GONE 2477 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) { 2478 boolean limitReached = maxDisplayedNotifications != -1 2479 && numShownItems >= maxDisplayedNotifications; 2480 final float viewHeight; 2481 if (limitReached) { 2482 viewHeight = mShelf.getIntrinsicHeight(); 2483 finish = true; 2484 } else { 2485 viewHeight = expandableView.getIntrinsicHeight(); 2486 } 2487 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount(); 2488 float padding; 2489 if (increasedPaddingAmount >= 0.0f) { 2490 padding = (int) NotificationUtils.interpolate( 2491 previousPaddingRequest, 2492 mIncreasedPaddingBetweenElements, 2493 increasedPaddingAmount); 2494 previousPaddingRequest = (int) NotificationUtils.interpolate( 2495 mPaddingBetweenElements, 2496 mIncreasedPaddingBetweenElements, 2497 increasedPaddingAmount); 2498 } else { 2499 int ownPadding = (int) NotificationUtils.interpolate( 2500 0, 2501 mPaddingBetweenElements, 2502 1.0f + increasedPaddingAmount); 2503 if (previousPaddingAmount > 0.0f) { 2504 padding = (int) NotificationUtils.interpolate( 2505 ownPadding, 2506 mIncreasedPaddingBetweenElements, 2507 previousPaddingAmount); 2508 } else { 2509 padding = ownPadding; 2510 } 2511 previousPaddingRequest = ownPadding; 2512 } 2513 if (height != 0) { 2514 height += padding; 2515 } 2516 height += calculateGapHeight(previousView, expandableView, numShownItems); 2517 previousPaddingAmount = increasedPaddingAmount; 2518 height += viewHeight; 2519 numShownItems++; 2520 previousView = expandableView; 2521 if (finish) { 2522 break; 2523 } 2524 } 2525 } 2526 mIntrinsicContentHeight = height; 2527 2528 // The topPadding can be bigger than the regular padding when qs is expanded, in that 2529 // state the maxPanelHeight and the contentHeight should be bigger 2530 mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; 2531 updateScrollability(); 2532 clampScrollPosition(); 2533 mAmbientState.setLayoutMaxHeight(mContentHeight); 2534 } 2535 2536 /** 2537 * Calculate the gap height between two different views 2538 * 2539 * @param previous the previousView 2540 * @param current the currentView 2541 * @param visibleIndex the visible index in the list 2542 * 2543 * @return the gap height needed before the current view 2544 */ 2545 public float calculateGapHeight( 2546 ExpandableView previous, 2547 ExpandableView current, 2548 int visibleIndex 2549 ) { 2550 return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, 2551 mAmbientState.getAnchorViewIndex(), visibleIndex, current, 2552 previous); 2553 } 2554 2555 @Override 2556 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2557 public boolean hasPulsingNotifications() { 2558 return mPulsing; 2559 } 2560 2561 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2562 private void updateScrollability() { 2563 boolean scrollable = !mQsExpanded && getScrollRange() > 0; 2564 if (scrollable != mScrollable) { 2565 mScrollable = scrollable; 2566 setFocusable(scrollable); 2567 updateForwardAndBackwardScrollability(); 2568 } 2569 } 2570 2571 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2572 private void updateForwardAndBackwardScrollability() { 2573 boolean forwardScrollable = mScrollable && !isScrolledToBottom(); 2574 boolean backwardsScrollable = mScrollable && !isScrolledToTop(); 2575 boolean changed = forwardScrollable != mForwardScrollable 2576 || backwardsScrollable != mBackwardScrollable; 2577 mForwardScrollable = forwardScrollable; 2578 mBackwardScrollable = backwardsScrollable; 2579 if (changed) { 2580 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 2581 } 2582 } 2583 2584 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2585 private void updateBackground() { 2586 // No need to update the background color if it's not being drawn. 2587 if (!mShouldDrawNotificationBackground) { 2588 return; 2589 } 2590 2591 updateBackgroundBounds(); 2592 if (didSectionBoundsChange()) { 2593 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop 2594 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating(); 2595 if (!isExpanded()) { 2596 abortBackgroundAnimators(); 2597 animate = false; 2598 } 2599 if (animate) { 2600 startBackgroundAnimation(); 2601 } else { 2602 for (NotificationSection section : mSections) { 2603 section.resetCurrentBounds(); 2604 } 2605 invalidate(); 2606 } 2607 } else { 2608 abortBackgroundAnimators(); 2609 } 2610 mAnimateNextBackgroundTop = false; 2611 mAnimateNextBackgroundBottom = false; 2612 mAnimateNextSectionBoundsChange = false; 2613 } 2614 2615 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2616 private void abortBackgroundAnimators() { 2617 for (NotificationSection section : mSections) { 2618 section.cancelAnimators(); 2619 } 2620 } 2621 2622 private boolean didSectionBoundsChange() { 2623 for (NotificationSection section : mSections) { 2624 if (section.didBoundsChange()) { 2625 return true; 2626 } 2627 } 2628 return false; 2629 } 2630 2631 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2632 private boolean areSectionBoundsAnimating() { 2633 for (NotificationSection section : mSections) { 2634 if (section.areBoundsAnimating()) { 2635 return true; 2636 } 2637 } 2638 return false; 2639 } 2640 2641 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2642 private void startBackgroundAnimation() { 2643 // TODO(kprevas): do we still need separate fields for top/bottom? 2644 // or can each section manage its own animation state? 2645 NotificationSection firstVisibleSection = getFirstVisibleSection(); 2646 NotificationSection lastVisibleSection = getLastVisibleSection(); 2647 for (NotificationSection section : mSections) { 2648 section.startBackgroundAnimation( 2649 section == firstVisibleSection 2650 ? mAnimateNextBackgroundTop 2651 : mAnimateNextSectionBoundsChange, 2652 section == lastVisibleSection 2653 ? mAnimateNextBackgroundBottom 2654 : mAnimateNextSectionBoundsChange); 2655 } 2656 } 2657 2658 /** 2659 * Update the background bounds to the new desired bounds 2660 */ 2661 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2662 private void updateBackgroundBounds() { 2663 int left = mSidePaddings; 2664 int right = getWidth() - mSidePaddings; 2665 for (NotificationSection section : mSections) { 2666 section.getBounds().left = left; 2667 section.getBounds().right = right; 2668 } 2669 2670 if (!mIsExpanded) { 2671 for (NotificationSection section : mSections) { 2672 section.getBounds().top = 0; 2673 section.getBounds().bottom = 0; 2674 } 2675 return; 2676 } 2677 int minTopPosition; 2678 NotificationSection lastSection = getLastVisibleSection(); 2679 boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD; 2680 if (!onKeyguard) { 2681 minTopPosition = (int) (mTopPadding + mStackTranslation); 2682 } else if (lastSection == null) { 2683 minTopPosition = mTopPadding; 2684 } else { 2685 // The first sections could be empty while there could still be elements in later 2686 // sections. The position of these first few sections is determined by the position of 2687 // the first visible section. 2688 NotificationSection firstVisibleSection = getFirstVisibleSection(); 2689 firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */, 2690 false /* shiftPulsingWithFirst */); 2691 minTopPosition = firstVisibleSection.getBounds().top; 2692 } 2693 boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1 2694 && (mAmbientState.isDozing() 2695 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard)); 2696 for (NotificationSection section : mSections) { 2697 int minBottomPosition = minTopPosition; 2698 if (section == lastSection) { 2699 // We need to make sure the section goes all the way to the shelf 2700 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf) 2701 + mShelf.getIntrinsicHeight()); 2702 } 2703 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition, 2704 shiftPulsingWithFirst); 2705 shiftPulsingWithFirst = false; 2706 } 2707 } 2708 2709 private NotificationSection getFirstVisibleSection() { 2710 for (NotificationSection section : mSections) { 2711 if (section.getFirstVisibleChild() != null) { 2712 return section; 2713 } 2714 } 2715 return null; 2716 } 2717 2718 private NotificationSection getLastVisibleSection() { 2719 for (int i = mSections.length - 1; i >= 0; i--) { 2720 NotificationSection section = mSections[i]; 2721 if (section.getLastVisibleChild() != null) { 2722 return section; 2723 } 2724 } 2725 return null; 2726 } 2727 2728 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2729 private ExpandableView getLastChildWithBackground() { 2730 int childCount = getChildCount(); 2731 for (int i = childCount - 1; i >= 0; i--) { 2732 ExpandableView child = (ExpandableView) getChildAt(i); 2733 if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) 2734 && child != mShelf) { 2735 return child; 2736 } 2737 } 2738 return null; 2739 } 2740 2741 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2742 private ExpandableView getFirstChildWithBackground() { 2743 int childCount = getChildCount(); 2744 for (int i = 0; i < childCount; i++) { 2745 ExpandableView child = (ExpandableView) getChildAt(i); 2746 if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) 2747 && child != mShelf) { 2748 return child; 2749 } 2750 } 2751 return null; 2752 } 2753 2754 //TODO: We shouldn't have to generate this list every time 2755 private List<ExpandableView> getChildrenWithBackground() { 2756 ArrayList<ExpandableView> children = new ArrayList<>(); 2757 int childCount = getChildCount(); 2758 for (int i = 0; i < childCount; i++) { 2759 ExpandableView child = (ExpandableView) getChildAt(i); 2760 if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) 2761 && child != mShelf) { 2762 children.add(child); 2763 } 2764 } 2765 2766 return children; 2767 } 2768 2769 /** 2770 * Fling the scroll view 2771 * 2772 * @param velocityY The initial velocity in the Y direction. Positive 2773 * numbers mean that the finger/cursor is moving down the screen, 2774 * which means we want to scroll towards the top. 2775 */ 2776 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2777 protected void fling(int velocityY) { 2778 if (getChildCount() > 0) { 2779 float topAmount = getCurrentOverScrollAmount(true); 2780 float bottomAmount = getCurrentOverScrollAmount(false); 2781 if (velocityY < 0 && topAmount > 0) { 2782 if (ANCHOR_SCROLLING) { 2783 mScrollAnchorViewY += topAmount; 2784 } else { 2785 setOwnScrollY(mOwnScrollY - (int) topAmount); 2786 } 2787 mDontReportNextOverScroll = true; 2788 setOverScrollAmount(0, true, false); 2789 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 2790 * mOverflingDistance + topAmount; 2791 } else if (velocityY > 0 && bottomAmount > 0) { 2792 if (ANCHOR_SCROLLING) { 2793 mScrollAnchorViewY -= bottomAmount; 2794 } else { 2795 setOwnScrollY((int) (mOwnScrollY + bottomAmount)); 2796 } 2797 setOverScrollAmount(0, false, false); 2798 mMaxOverScroll = Math.abs(velocityY) / 1000f 2799 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 2800 + bottomAmount; 2801 } else { 2802 // it will be set once we reach the boundary 2803 mMaxOverScroll = 0.0f; 2804 } 2805 if (ANCHOR_SCROLLING) { 2806 flingScroller(velocityY); 2807 } else { 2808 int scrollRange = getScrollRange(); 2809 int minScrollY = Math.max(0, scrollRange); 2810 if (mExpandedInThisMotion) { 2811 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); 2812 } 2813 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0, 2814 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); 2815 } 2816 2817 animateScroll(); 2818 } 2819 } 2820 2821 /** 2822 * Flings the overscroller with the given velocity (anchor-based scrolling). 2823 * 2824 * Because anchor-based scrolling can't track the current scroll position, the overscroller is 2825 * always started at startY = 0, and we interpret the positions it computes as relative to the 2826 * start of the scroll. 2827 */ 2828 private void flingScroller(int velocityY) { 2829 assert ANCHOR_SCROLLING; 2830 mIsScrollerBoundSet = false; 2831 maybeFlingScroller(velocityY, true /* always fling */); 2832 } 2833 2834 private void maybeFlingScroller(int velocityY, boolean alwaysFling) { 2835 assert ANCHOR_SCROLLING; 2836 // Attempt to determine the maximum amount to scroll before we reach the end. 2837 // If the first view is not materialized (for an upwards scroll) or the last view is either 2838 // not materialized or is pinned to the shade (for a downwards scroll), we don't know this 2839 // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update 2840 // the scroller once we approach the start/end of the list. 2841 int minY = Integer.MIN_VALUE; 2842 int maxY = Integer.MAX_VALUE; 2843 if (velocityY < 0) { 2844 minY = getMaxNegativeScrollAmount(); 2845 if (minY > Integer.MIN_VALUE) { 2846 mIsScrollerBoundSet = true; 2847 } 2848 } else { 2849 maxY = getMaxPositiveScrollAmount(); 2850 if (maxY < Integer.MAX_VALUE) { 2851 mIsScrollerBoundSet = true; 2852 } 2853 } 2854 if (mIsScrollerBoundSet || alwaysFling) { 2855 mLastScrollerY = 0; 2856 // x velocity is set to 1 to avoid overscroller bug 2857 mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0, 2858 mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2); 2859 } 2860 } 2861 2862 /** 2863 * Returns the maximum number of pixels we can scroll in the positive direction (downwards) 2864 * before reaching the bottom of the list (discounting overscroll). 2865 * 2866 * If the return value is negative then we have overscrolled; this is a transient state which 2867 * should immediately be handled by adjusting the anchor position and adding the extra space to 2868 * the bottom overscroll amount. 2869 * 2870 * If we don't know how many pixels we have left to scroll (because the last row has not been 2871 * materialized, or it's in the shelf so it doesn't have its "natural" position), we return 2872 * {@link Integer#MAX_VALUE}. 2873 */ 2874 private int getMaxPositiveScrollAmount() { 2875 assert ANCHOR_SCROLLING; 2876 // TODO: once we're recycling we need to check the adapter position of the last child. 2877 ExpandableNotificationRow lastRow = getLastRowNotGone(); 2878 if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) { 2879 // distance from bottom of last child to bottom of notifications area is: 2880 // distance from bottom of last child 2881 return (int) (lastRow.getTranslationY() + lastRow.getActualHeight() 2882 // to top of anchor view 2883 - mScrollAnchorView.getTranslationY() 2884 // plus distance from anchor view to top of notifications area 2885 + mScrollAnchorViewY 2886 // minus height of notifications area. 2887 - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight())); 2888 } else { 2889 return Integer.MAX_VALUE; 2890 } 2891 } 2892 2893 /** 2894 * Returns the maximum number of pixels (as a negative number) we can scroll in the negative 2895 * direction (upwards) before reaching the top of the list (discounting overscroll). 2896 * 2897 * If the return value is positive then we have overscrolled; this is a transient state which 2898 * should immediately be handled by adjusting the anchor position and adding the extra space to 2899 * the top overscroll amount. 2900 * 2901 * If we don't know how many pixels we have left to scroll (because the first row has not been 2902 * materialized), we return {@link Integer#MIN_VALUE}. 2903 */ 2904 private int getMaxNegativeScrollAmount() { 2905 assert ANCHOR_SCROLLING; 2906 // TODO: once we're recycling we need to check the adapter position of the first child. 2907 ExpandableView firstChild = getFirstChildNotGone(); 2908 if (mScrollAnchorView != null && firstChild != null) { 2909 // distance from top of first child to top of notifications area is: 2910 // distance from top of anchor view 2911 return (int) -(mScrollAnchorView.getTranslationY() 2912 // to top of first child 2913 - firstChild.getTranslationY() 2914 // minus distance from top of anchor view to top of notifications area. 2915 - mScrollAnchorViewY); 2916 } else { 2917 return Integer.MIN_VALUE; 2918 } 2919 } 2920 2921 /** 2922 * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view 2923 * not being materialized or being pinned to the shelf, we need to check on every frame if we're 2924 * able to set the bounds. If we are, we fling the scroller again with the newly computed 2925 * bounds. 2926 */ 2927 private void maybeReflingScroller() { 2928 if (!mIsScrollerBoundSet) { 2929 // Because mScroller is a flywheel scroller, we fling with the minimum possible 2930 // velocity to establish direction, so as not to perceptibly affect the velocity. 2931 maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()), 2932 false /* alwaysFling */); 2933 } 2934 } 2935 2936 /** 2937 * @return Whether a fling performed on the top overscroll edge lead to the expanded 2938 * overScroll view (i.e QS). 2939 */ 2940 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2941 private boolean shouldOverScrollFling(int initialVelocity) { 2942 float topOverScroll = getCurrentOverScrollAmount(true); 2943 return mScrolledToTopOnFirstDown 2944 && !mExpandedInThisMotion 2945 && topOverScroll > mMinTopOverScrollToEscape 2946 && initialVelocity > 0; 2947 } 2948 2949 /** 2950 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into 2951 * account. 2952 * 2953 * @param qsHeight the top padding imposed by the quick settings panel 2954 * @param animate whether to animate the change 2955 */ 2956 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2957 public void updateTopPadding(float qsHeight, boolean animate) { 2958 int topPadding = (int) qsHeight; 2959 int minStackHeight = getLayoutMinHeight(); 2960 if (topPadding + minStackHeight > getHeight()) { 2961 mTopPaddingOverflow = topPadding + minStackHeight - getHeight(); 2962 } else { 2963 mTopPaddingOverflow = 0; 2964 } 2965 setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled()); 2966 setExpandedHeight(mExpandedHeight); 2967 } 2968 2969 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2970 public void setMaxTopPadding(int maxTopPadding) { 2971 mMaxTopPadding = maxTopPadding; 2972 } 2973 2974 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2975 public int getLayoutMinHeight() { 2976 if (isHeadsUpTransition()) { 2977 ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow(); 2978 if (trackedHeadsUpRow.isAboveShelf()) { 2979 int hunDistance = (int) MathUtils.lerp( 2980 0, 2981 getPositionInLinearLayout(trackedHeadsUpRow), 2982 mAmbientState.getAppearFraction()); 2983 return getTopHeadsUpPinnedHeight() + hunDistance; 2984 } else { 2985 return getTopHeadsUpPinnedHeight(); 2986 } 2987 } 2988 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); 2989 } 2990 2991 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2992 public float getTopPaddingOverflow() { 2993 return mTopPaddingOverflow; 2994 } 2995 2996 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2997 public int getPeekHeight() { 2998 final ExpandableView firstChild = getFirstChildNotGone(); 2999 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight() 3000 : mCollapsedSize; 3001 int shelfHeight = 0; 3002 if (getLastVisibleSection() != null && mShelf.getVisibility() != GONE) { 3003 shelfHeight = mShelf.getIntrinsicHeight(); 3004 } 3005 return mIntrinsicPadding + firstChildMinHeight + shelfHeight; 3006 } 3007 3008 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3009 private int clampPadding(int desiredPadding) { 3010 return Math.max(desiredPadding, mIntrinsicPadding); 3011 } 3012 3013 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3014 private float getRubberBandFactor(boolean onTop) { 3015 if (!onTop) { 3016 return RUBBER_BAND_FACTOR_NORMAL; 3017 } 3018 if (mExpandedInThisMotion) { 3019 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 3020 } else if (mIsExpansionChanging || mPanelTracking) { 3021 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 3022 } else if (mScrolledToTopOnFirstDown) { 3023 return 1.0f; 3024 } 3025 return RUBBER_BAND_FACTOR_NORMAL; 3026 } 3027 3028 /** 3029 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 3030 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 3031 * overscroll view (e.g. expand QS). 3032 */ 3033 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3034 private boolean isRubberbanded(boolean onTop) { 3035 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking 3036 || !mScrolledToTopOnFirstDown; 3037 } 3038 3039 3040 3041 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3042 public void setChildTransferInProgress(boolean childTransferInProgress) { 3043 Assert.isMainThread(); 3044 mChildTransferInProgress = childTransferInProgress; 3045 } 3046 3047 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3048 @Override 3049 public void onViewRemoved(View child) { 3050 super.onViewRemoved(child); 3051 // we only call our internal methods if this is actually a removal and not just a 3052 // notification which becomes a child notification 3053 if (!mChildTransferInProgress) { 3054 onViewRemovedInternal((ExpandableView) child, this); 3055 } 3056 } 3057 3058 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3059 @Override 3060 public void cleanUpViewStateForEntry(NotificationEntry entry) { 3061 View child = entry.getRow(); 3062 if (child == mSwipeHelper.getTranslatingParentView()) { 3063 mSwipeHelper.clearTranslatingParentView(); 3064 } 3065 } 3066 3067 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3068 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) { 3069 if (mChangePositionInProgress) { 3070 // This is only a position change, don't do anything special 3071 return; 3072 } 3073 child.setOnHeightChangedListener(null); 3074 updateScrollStateForRemovedChild(child); 3075 boolean animationGenerated = generateRemoveAnimation(child); 3076 if (animationGenerated) { 3077 if (!mSwipedOutViews.contains(child) 3078 || Math.abs(child.getTranslation()) != child.getWidth()) { 3079 container.addTransientView(child, 0); 3080 child.setTransientContainer(container); 3081 } 3082 } else { 3083 mSwipedOutViews.remove(child); 3084 } 3085 updateAnimationState(false, child); 3086 3087 focusNextViewIfFocused(child); 3088 } 3089 3090 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3091 private void focusNextViewIfFocused(View view) { 3092 if (view instanceof ExpandableNotificationRow) { 3093 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3094 if (row.shouldRefocusOnDismiss()) { 3095 View nextView = row.getChildAfterViewWhenDismissed(); 3096 if (nextView == null) { 3097 View groupParentWhenDismissed = row.getGroupParentWhenDismissed(); 3098 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null 3099 ? groupParentWhenDismissed.getTranslationY() 3100 : view.getTranslationY(), true /* ignoreChildren */); 3101 } 3102 if (nextView != null) { 3103 nextView.requestAccessibilityFocus(); 3104 } 3105 } 3106 } 3107 3108 } 3109 3110 @ShadeViewRefactor(RefactorComponent.ADAPTER) 3111 private boolean isChildInGroup(View child) { 3112 return child instanceof ExpandableNotificationRow 3113 && mGroupManager.isChildInGroupWithSummary( 3114 ((ExpandableNotificationRow) child).getEntry().getSbn()); 3115 } 3116 3117 /** 3118 * Generate a remove animation for a child view. 3119 * 3120 * @param child The view to generate the remove animation for. 3121 * @return Whether an animation was generated. 3122 */ 3123 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3124 private boolean generateRemoveAnimation(ExpandableView child) { 3125 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { 3126 mAddedHeadsUpChildren.remove(child); 3127 return false; 3128 } 3129 if (isClickedHeadsUp(child)) { 3130 // An animation is already running, add it transiently 3131 mClearTransientViewsWhenFinished.add(child); 3132 return true; 3133 } 3134 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { 3135 if (!mChildrenToAddAnimated.contains(child)) { 3136 // Generate Animations 3137 mChildrenToRemoveAnimated.add(child); 3138 mNeedsAnimation = true; 3139 return true; 3140 } else { 3141 mChildrenToAddAnimated.remove(child); 3142 mFromMoreCardAdditions.remove(child); 3143 return false; 3144 } 3145 } 3146 return false; 3147 } 3148 3149 @ShadeViewRefactor(RefactorComponent.ADAPTER) 3150 private boolean isClickedHeadsUp(View child) { 3151 return HeadsUpUtil.isClickedHeadsUpNotification(child); 3152 } 3153 3154 /** 3155 * Remove a removed child view from the heads up animations if it was just added there 3156 * 3157 * @return whether any child was removed from the list to animate 3158 */ 3159 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3160 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) { 3161 boolean hasAddEvent = false; 3162 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 3163 ExpandableNotificationRow row = eventPair.first; 3164 boolean isHeadsUp = eventPair.second; 3165 if (child == row) { 3166 mTmpList.add(eventPair); 3167 hasAddEvent |= isHeadsUp; 3168 } 3169 } 3170 if (hasAddEvent) { 3171 // This child was just added lets remove all events. 3172 mHeadsUpChangeAnimations.removeAll(mTmpList); 3173 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false); 3174 } 3175 mTmpList.clear(); 3176 return hasAddEvent; 3177 } 3178 3179 /** 3180 * @param child the child to query 3181 * @return whether a view is not a top level child but a child notification and that group is 3182 * not expanded 3183 */ 3184 @ShadeViewRefactor(RefactorComponent.ADAPTER) 3185 private boolean isChildInInvisibleGroup(View child) { 3186 if (child instanceof ExpandableNotificationRow) { 3187 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3188 NotificationEntry groupSummary = 3189 mGroupManager.getGroupSummary(row.getEntry().getSbn()); 3190 if (groupSummary != null && groupSummary.getRow() != row) { 3191 return row.getVisibility() == View.INVISIBLE; 3192 } 3193 } 3194 return false; 3195 } 3196 3197 /** 3198 * Updates the scroll position when a child was removed 3199 * 3200 * @param removedChild the removed child 3201 */ 3202 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3203 private void updateScrollStateForRemovedChild(ExpandableView removedChild) { 3204 if (ANCHOR_SCROLLING) { 3205 if (removedChild == mScrollAnchorView) { 3206 ExpandableView firstChild = getFirstChildNotGone(); 3207 if (firstChild != null) { 3208 mScrollAnchorView = firstChild; 3209 } else { 3210 mScrollAnchorView = mShelf; 3211 } 3212 // Adjust anchor view Y by the distance between the old and new anchors 3213 // so that there's no visible change. 3214 mScrollAnchorViewY += 3215 mScrollAnchorView.getTranslationY() - removedChild.getTranslationY(); 3216 } 3217 updateScrollAnchor(); 3218 // TODO: once we're recycling this will need to check the adapter position of the child 3219 if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) { 3220 mScrollAnchorViewY = 0; 3221 } 3222 updateOnScrollChange(); 3223 } else { 3224 int startingPosition = getPositionInLinearLayout(removedChild); 3225 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount(); 3226 int padding; 3227 if (increasedPaddingAmount >= 0) { 3228 padding = (int) NotificationUtils.interpolate( 3229 mPaddingBetweenElements, 3230 mIncreasedPaddingBetweenElements, 3231 increasedPaddingAmount); 3232 } else { 3233 padding = (int) NotificationUtils.interpolate( 3234 0, 3235 mPaddingBetweenElements, 3236 1.0f + increasedPaddingAmount); 3237 } 3238 int childHeight = getIntrinsicHeight(removedChild) + padding; 3239 int endPosition = startingPosition + childHeight; 3240 if (endPosition <= mOwnScrollY) { 3241 // This child is fully scrolled of the top, so we have to deduct its height from the 3242 // scrollPosition 3243 setOwnScrollY(mOwnScrollY - childHeight); 3244 } else if (startingPosition < mOwnScrollY) { 3245 // This child is currently being scrolled into, set the scroll position to the 3246 // start of this child 3247 setOwnScrollY(startingPosition); 3248 } 3249 } 3250 } 3251 3252 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3253 private int getIntrinsicHeight(View view) { 3254 if (view instanceof ExpandableView) { 3255 ExpandableView expandableView = (ExpandableView) view; 3256 return expandableView.getIntrinsicHeight(); 3257 } 3258 return view.getHeight(); 3259 } 3260 3261 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3262 public int getPositionInLinearLayout(View requestedView) { 3263 ExpandableNotificationRow childInGroup = null; 3264 ExpandableNotificationRow requestedRow = null; 3265 if (isChildInGroup(requestedView)) { 3266 // We're asking for a child in a group. Calculate the position of the parent first, 3267 // then within the parent. 3268 childInGroup = (ExpandableNotificationRow) requestedView; 3269 requestedView = requestedRow = childInGroup.getNotificationParent(); 3270 } 3271 int position = 0; 3272 float previousPaddingRequest = mPaddingBetweenElements; 3273 float previousPaddingAmount = 0.0f; 3274 for (int i = 0; i < getChildCount(); i++) { 3275 ExpandableView child = (ExpandableView) getChildAt(i); 3276 boolean notGone = child.getVisibility() != View.GONE; 3277 if (notGone && !child.hasNoContentHeight()) { 3278 float increasedPaddingAmount = child.getIncreasedPaddingAmount(); 3279 float padding; 3280 if (increasedPaddingAmount >= 0.0f) { 3281 padding = (int) NotificationUtils.interpolate( 3282 previousPaddingRequest, 3283 mIncreasedPaddingBetweenElements, 3284 increasedPaddingAmount); 3285 previousPaddingRequest = (int) NotificationUtils.interpolate( 3286 mPaddingBetweenElements, 3287 mIncreasedPaddingBetweenElements, 3288 increasedPaddingAmount); 3289 } else { 3290 int ownPadding = (int) NotificationUtils.interpolate( 3291 0, 3292 mPaddingBetweenElements, 3293 1.0f + increasedPaddingAmount); 3294 if (previousPaddingAmount > 0.0f) { 3295 padding = (int) NotificationUtils.interpolate( 3296 ownPadding, 3297 mIncreasedPaddingBetweenElements, 3298 previousPaddingAmount); 3299 } else { 3300 padding = ownPadding; 3301 } 3302 previousPaddingRequest = ownPadding; 3303 } 3304 if (position != 0) { 3305 position += padding; 3306 } 3307 previousPaddingAmount = increasedPaddingAmount; 3308 } 3309 if (child == requestedView) { 3310 if (requestedRow != null) { 3311 position += requestedRow.getPositionOfChild(childInGroup); 3312 } 3313 return position; 3314 } 3315 if (notGone) { 3316 position += getIntrinsicHeight(child); 3317 } 3318 } 3319 return 0; 3320 } 3321 3322 @Override 3323 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3324 public void onViewAdded(View child) { 3325 super.onViewAdded(child); 3326 onViewAddedInternal((ExpandableView) child); 3327 } 3328 3329 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3330 private void updateFirstAndLastBackgroundViews() { 3331 NotificationSection firstSection = getFirstVisibleSection(); 3332 NotificationSection lastSection = getLastVisibleSection(); 3333 ExpandableView previousFirstChild = 3334 firstSection == null ? null : firstSection.getFirstVisibleChild(); 3335 ExpandableView previousLastChild = 3336 lastSection == null ? null : lastSection.getLastVisibleChild(); 3337 3338 ExpandableView firstChild = getFirstChildWithBackground(); 3339 ExpandableView lastChild = getLastChildWithBackground(); 3340 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections( 3341 mSections, getChildrenWithBackground()); 3342 3343 if (mAnimationsEnabled && mIsExpanded) { 3344 mAnimateNextBackgroundTop = firstChild != previousFirstChild; 3345 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout; 3346 mAnimateNextSectionBoundsChange = sectionViewsChanged; 3347 } else { 3348 mAnimateNextBackgroundTop = false; 3349 mAnimateNextBackgroundBottom = false; 3350 mAnimateNextSectionBoundsChange = false; 3351 } 3352 mAmbientState.setLastVisibleBackgroundChild(lastChild); 3353 mRoundnessManager.updateRoundedChildren(mSections); 3354 mAnimateBottomOnLayout = false; 3355 invalidate(); 3356 } 3357 3358 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3359 private void onViewAddedInternal(ExpandableView child) { 3360 updateHideSensitiveForChild(child); 3361 child.setOnHeightChangedListener(this); 3362 generateAddAnimation(child, false /* fromMoreCard */); 3363 updateAnimationState(child); 3364 updateChronometerForChild(child); 3365 if (child instanceof ExpandableNotificationRow) { 3366 ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); 3367 } 3368 if (ANCHOR_SCROLLING) { 3369 // TODO: once we're recycling this will need to check the adapter position of the child 3370 if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) { 3371 // New child was added at the top while we're scrolled to the top; 3372 // make it the new anchor view so that we stay at the top. 3373 mScrollAnchorView = child; 3374 } 3375 } 3376 } 3377 3378 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3379 private void updateHideSensitiveForChild(ExpandableView child) { 3380 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive()); 3381 } 3382 3383 @Override 3384 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3385 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) { 3386 onViewRemovedInternal(row, childrenContainer); 3387 } 3388 3389 @Override 3390 public void notifyGroupChildRemoved(View child, ViewGroup parent) { 3391 notifyGroupChildRemoved((ExpandableView) child, parent); 3392 } 3393 3394 @Override 3395 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3396 public void notifyGroupChildAdded(ExpandableView row) { 3397 onViewAddedInternal(row); 3398 } 3399 3400 @Override 3401 public void notifyGroupChildAdded(View view) { 3402 notifyGroupChildAdded((ExpandableView) view); 3403 } 3404 3405 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3406 public void setAnimationsEnabled(boolean animationsEnabled) { 3407 mAnimationsEnabled = animationsEnabled; 3408 updateNotificationAnimationStates(); 3409 if (!animationsEnabled) { 3410 mSwipedOutViews.clear(); 3411 mChildrenToRemoveAnimated.clear(); 3412 clearTemporaryViewsInGroup(this); 3413 } 3414 } 3415 3416 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3417 private void updateNotificationAnimationStates() { 3418 boolean running = mAnimationsEnabled || hasPulsingNotifications(); 3419 mShelf.setAnimationsEnabled(running); 3420 int childCount = getChildCount(); 3421 for (int i = 0; i < childCount; i++) { 3422 View child = getChildAt(i); 3423 running &= mIsExpanded || isPinnedHeadsUp(child); 3424 updateAnimationState(running, child); 3425 } 3426 } 3427 3428 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3429 private void updateAnimationState(View child) { 3430 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications()) 3431 && (mIsExpanded || isPinnedHeadsUp(child)), child); 3432 } 3433 3434 @Override 3435 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3436 public void setExpandingNotification(ExpandableNotificationRow row) { 3437 mAmbientState.setExpandingNotification(row); 3438 requestChildrenUpdate(); 3439 } 3440 3441 @Override 3442 @ShadeViewRefactor(RefactorComponent.ADAPTER) 3443 public void bindRow(ExpandableNotificationRow row) { 3444 row.setHeadsUpAnimatingAwayListener(animatingAway -> { 3445 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway); 3446 mHeadsUpAppearanceController.updateHeader(row.getEntry()); 3447 }); 3448 } 3449 3450 @Override 3451 public boolean containsView(View v) { 3452 return v.getParent() == this; 3453 } 3454 3455 @Override 3456 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3457 public void applyExpandAnimationParams(ExpandAnimationParameters params) { 3458 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange()); 3459 requestChildrenUpdate(); 3460 } 3461 3462 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3463 private void updateAnimationState(boolean running, View child) { 3464 if (child instanceof ExpandableNotificationRow) { 3465 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3466 row.setIconAnimationRunning(running); 3467 } 3468 } 3469 3470 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3471 public boolean isAddOrRemoveAnimationPending() { 3472 return mNeedsAnimation 3473 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 3474 } 3475 3476 @Override 3477 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3478 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { 3479 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) { 3480 // Generate Animations 3481 mChildrenToAddAnimated.add(child); 3482 if (fromMoreCard) { 3483 mFromMoreCardAdditions.add(child); 3484 } 3485 mNeedsAnimation = true; 3486 } 3487 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress 3488 && !isFullyHidden()) { 3489 mAddedHeadsUpChildren.add(child); 3490 mChildrenToAddAnimated.remove(child); 3491 } 3492 } 3493 3494 @Override 3495 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3496 public void changeViewPosition(ExpandableView child, int newIndex) { 3497 Assert.isMainThread(); 3498 if (mChangePositionInProgress) { 3499 throw new IllegalStateException("Reentrant call to changeViewPosition"); 3500 } 3501 3502 int currentIndex = indexOfChild(child); 3503 3504 if (currentIndex == -1) { 3505 boolean isTransient = false; 3506 if (child instanceof ExpandableNotificationRow 3507 && child.getTransientContainer() != null) { 3508 isTransient = true; 3509 } 3510 Log.e(TAG, "Attempting to re-position " 3511 + (isTransient ? "transient" : "") 3512 + " view {" 3513 + child 3514 + "}"); 3515 return; 3516 } 3517 3518 if (child != null && child.getParent() == this && currentIndex != newIndex) { 3519 mChangePositionInProgress = true; 3520 child.setChangingPosition(true); 3521 removeView(child); 3522 addView(child, newIndex); 3523 child.setChangingPosition(false); 3524 mChangePositionInProgress = false; 3525 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 3526 mChildrenChangingPositions.add(child); 3527 mNeedsAnimation = true; 3528 } 3529 } 3530 } 3531 3532 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3533 private void startAnimationToState() { 3534 if (mNeedsAnimation) { 3535 generateAllAnimationEvents(); 3536 mNeedsAnimation = false; 3537 } 3538 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 3539 setAnimationRunning(true); 3540 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay); 3541 mAnimationEvents.clear(); 3542 updateBackground(); 3543 updateViewShadows(); 3544 updateClippingToTopRoundedCorner(); 3545 } else { 3546 applyCurrentState(); 3547 } 3548 mGoToFullShadeDelay = 0; 3549 } 3550 3551 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3552 private void generateAllAnimationEvents() { 3553 generateHeadsUpAnimationEvents(); 3554 generateChildRemovalEvents(); 3555 generateChildAdditionEvents(); 3556 generatePositionChangeEvents(); 3557 generateTopPaddingEvent(); 3558 generateActivateEvent(); 3559 generateDimmedEvent(); 3560 generateHideSensitiveEvent(); 3561 generateGoToFullShadeEvent(); 3562 generateViewResizeEvent(); 3563 generateGroupExpansionEvent(); 3564 generateAnimateEverythingEvent(); 3565 } 3566 3567 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3568 private void generateHeadsUpAnimationEvents() { 3569 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 3570 ExpandableNotificationRow row = eventPair.first; 3571 boolean isHeadsUp = eventPair.second; 3572 if (isHeadsUp != row.isHeadsUp()) { 3573 // For cases where we have a heads up showing and appearing again we shouldn't 3574 // do the animations at all. 3575 continue; 3576 } 3577 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; 3578 boolean onBottom = false; 3579 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded; 3580 boolean performDisappearAnimation = !mIsExpanded 3581 // Only animate if we still have pinned heads up, otherwise we just have the 3582 // regular collapse animation of the lock screen 3583 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard() 3584 && mHeadsUpManager.hasPinnedHeadsUp()); 3585 if (performDisappearAnimation && !isHeadsUp) { 3586 type = row.wasJustClicked() 3587 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 3588 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; 3589 if (row.isChildInGroup()) { 3590 // We can otherwise get stuck in there if it was just isolated 3591 row.setHeadsUpAnimatingAway(false); 3592 continue; 3593 } 3594 } else { 3595 ExpandableViewState viewState = row.getViewState(); 3596 if (viewState == null) { 3597 // A view state was never generated for this view, so we don't need to animate 3598 // this. This may happen with notification children. 3599 continue; 3600 } 3601 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { 3602 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { 3603 // Our custom add animation 3604 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 3605 } else { 3606 // Normal add animation 3607 type = AnimationEvent.ANIMATION_TYPE_ADD; 3608 } 3609 onBottom = !pinnedAndClosed; 3610 } 3611 } 3612 AnimationEvent event = new AnimationEvent(row, type); 3613 event.headsUpFromBottom = onBottom; 3614 mAnimationEvents.add(event); 3615 } 3616 mHeadsUpChangeAnimations.clear(); 3617 mAddedHeadsUpChildren.clear(); 3618 } 3619 3620 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 3621 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { 3622 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { 3623 return false; 3624 } 3625 return true; 3626 } 3627 3628 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3629 private void generateGroupExpansionEvent() { 3630 // Generate a group expansion/collapsing event if there is such a group at all 3631 if (mExpandedGroupView != null) { 3632 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, 3633 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); 3634 mExpandedGroupView = null; 3635 } 3636 } 3637 3638 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3639 private void generateViewResizeEvent() { 3640 if (mNeedViewResizeAnimation) { 3641 boolean hasDisappearAnimation = false; 3642 for (AnimationEvent animationEvent : mAnimationEvents) { 3643 final int type = animationEvent.animationType; 3644 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 3645 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { 3646 hasDisappearAnimation = true; 3647 break; 3648 } 3649 } 3650 3651 if (!hasDisappearAnimation) { 3652 mAnimationEvents.add( 3653 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); 3654 } 3655 } 3656 mNeedViewResizeAnimation = false; 3657 } 3658 3659 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3660 private void generateChildRemovalEvents() { 3661 for (ExpandableView child : mChildrenToRemoveAnimated) { 3662 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 3663 3664 // we need to know the view after this one 3665 float removedTranslation = child.getTranslationY(); 3666 boolean ignoreChildren = true; 3667 if (child instanceof ExpandableNotificationRow) { 3668 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3669 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) { 3670 removedTranslation = row.getTranslationWhenRemoved(); 3671 ignoreChildren = false; 3672 } 3673 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); 3674 } else if (child instanceof MediaHeaderView) { 3675 childWasSwipedOut = true; 3676 } 3677 if (!childWasSwipedOut) { 3678 Rect clipBounds = child.getClipBounds(); 3679 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; 3680 3681 if (childWasSwipedOut && child instanceof ExpandableView) { 3682 // Clean up any potential transient views if the child has already been swiped 3683 // out, as we won't be animating it further (due to its height already being 3684 // clipped to 0. 3685 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer(); 3686 if (transientContainer != null) { 3687 transientContainer.removeTransientView(child); 3688 } 3689 } 3690 } 3691 int animationType = childWasSwipedOut 3692 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 3693 : AnimationEvent.ANIMATION_TYPE_REMOVE; 3694 AnimationEvent event = new AnimationEvent(child, animationType); 3695 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation, 3696 ignoreChildren); 3697 mAnimationEvents.add(event); 3698 mSwipedOutViews.remove(child); 3699 } 3700 mChildrenToRemoveAnimated.clear(); 3701 } 3702 3703 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3704 private void generatePositionChangeEvents() { 3705 for (ExpandableView child : mChildrenChangingPositions) { 3706 Integer duration = null; 3707 if (child instanceof ExpandableNotificationRow) { 3708 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3709 if (row.getEntry().isMarkedForUserTriggeredMovement()) { 3710 duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE; 3711 row.getEntry().markForUserTriggeredMovement(false); 3712 } 3713 } 3714 AnimationEvent animEvent = duration == null 3715 ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION) 3716 : new AnimationEvent( 3717 child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration); 3718 mAnimationEvents.add(animEvent); 3719 } 3720 mChildrenChangingPositions.clear(); 3721 if (mGenerateChildOrderChangedEvent) { 3722 mAnimationEvents.add(new AnimationEvent(null, 3723 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 3724 mGenerateChildOrderChangedEvent = false; 3725 } 3726 } 3727 3728 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3729 private void generateChildAdditionEvents() { 3730 for (ExpandableView child : mChildrenToAddAnimated) { 3731 if (mFromMoreCardAdditions.contains(child)) { 3732 mAnimationEvents.add(new AnimationEvent(child, 3733 AnimationEvent.ANIMATION_TYPE_ADD, 3734 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 3735 } else { 3736 mAnimationEvents.add(new AnimationEvent(child, 3737 AnimationEvent.ANIMATION_TYPE_ADD)); 3738 } 3739 } 3740 mChildrenToAddAnimated.clear(); 3741 mFromMoreCardAdditions.clear(); 3742 } 3743 3744 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3745 private void generateTopPaddingEvent() { 3746 if (mTopPaddingNeedsAnimation) { 3747 AnimationEvent event; 3748 if (mAmbientState.isDozing()) { 3749 event = new AnimationEvent(null /* view */, 3750 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED, 3751 KeyguardSliceView.DEFAULT_ANIM_DURATION); 3752 } else { 3753 event = new AnimationEvent(null /* view */, 3754 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED); 3755 } 3756 mAnimationEvents.add(event); 3757 } 3758 mTopPaddingNeedsAnimation = false; 3759 } 3760 3761 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3762 private void generateActivateEvent() { 3763 if (mActivateNeedsAnimation) { 3764 mAnimationEvents.add( 3765 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 3766 } 3767 mActivateNeedsAnimation = false; 3768 } 3769 3770 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3771 private void generateAnimateEverythingEvent() { 3772 if (mEverythingNeedsAnimation) { 3773 mAnimationEvents.add( 3774 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 3775 } 3776 mEverythingNeedsAnimation = false; 3777 } 3778 3779 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3780 private void generateDimmedEvent() { 3781 if (mDimmedNeedsAnimation) { 3782 mAnimationEvents.add( 3783 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 3784 } 3785 mDimmedNeedsAnimation = false; 3786 } 3787 3788 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3789 private void generateHideSensitiveEvent() { 3790 if (mHideSensitiveNeedsAnimation) { 3791 mAnimationEvents.add( 3792 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 3793 } 3794 mHideSensitiveNeedsAnimation = false; 3795 } 3796 3797 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 3798 private void generateGoToFullShadeEvent() { 3799 if (mGoToFullShadeNeedsAnimation) { 3800 mAnimationEvents.add( 3801 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 3802 } 3803 mGoToFullShadeNeedsAnimation = false; 3804 } 3805 3806 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 3807 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { 3808 return new StackScrollAlgorithm(context, this); 3809 } 3810 3811 /** 3812 * @return Whether a y coordinate is inside the content. 3813 */ 3814 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 3815 public boolean isInContentBounds(float y) { 3816 return y < getHeight() - getEmptyBottomMargin(); 3817 } 3818 3819 @ShadeViewRefactor(RefactorComponent.INPUT) 3820 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { 3821 mLongPressListener = listener; 3822 } 3823 3824 private float getTouchSlop(MotionEvent event) { 3825 // Adjust the touch slop if another gesture may be being performed. 3826 return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE 3827 ? mTouchSlop * mSlopMultiplier 3828 : mTouchSlop; 3829 } 3830 3831 @Override 3832 @ShadeViewRefactor(RefactorComponent.INPUT) 3833 public boolean onTouchEvent(MotionEvent ev) { 3834 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 3835 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 3836 || ev.getActionMasked() == MotionEvent.ACTION_UP; 3837 handleEmptySpaceClick(ev); 3838 boolean expandWantsIt = false; 3839 boolean swipingInProgress = mSwipingInProgress; 3840 if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) { 3841 if (isCancelOrUp) { 3842 mExpandHelper.onlyObserveMovements(false); 3843 } 3844 boolean wasExpandingBefore = mExpandingNotification; 3845 expandWantsIt = mExpandHelper.onTouchEvent(ev); 3846 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore 3847 && !mDisallowScrollingInThisMotion) { 3848 dispatchDownEventToScroller(ev); 3849 } 3850 } 3851 boolean scrollerWantsIt = false; 3852 if (mIsExpanded && !swipingInProgress && !mExpandingNotification 3853 && !mDisallowScrollingInThisMotion) { 3854 scrollerWantsIt = onScrollTouch(ev); 3855 } 3856 boolean horizontalSwipeWantsIt = false; 3857 if (!mIsBeingDragged 3858 && !mExpandingNotification 3859 && !mExpandedInThisMotion 3860 && !mOnlyScrollingInThisMotion 3861 && !mDisallowDismissInThisMotion) { 3862 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 3863 } 3864 3865 // Check if we need to clear any snooze leavebehinds 3866 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) 3867 && guts.getGutsContent() instanceof NotificationSnooze) { 3868 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); 3869 if ((ns.isExpanded() && isCancelOrUp) 3870 || (!horizontalSwipeWantsIt && scrollerWantsIt)) { 3871 // If the leavebehind is expanded we clear it on the next up event, otherwise we 3872 // clear it on the next non-horizontal swipe or expand event. 3873 checkSnoozeLeavebehind(); 3874 } 3875 } 3876 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 3877 mCheckForLeavebehind = true; 3878 } 3879 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); 3880 } 3881 3882 @ShadeViewRefactor(RefactorComponent.INPUT) 3883 private void dispatchDownEventToScroller(MotionEvent ev) { 3884 MotionEvent downEvent = MotionEvent.obtain(ev); 3885 downEvent.setAction(MotionEvent.ACTION_DOWN); 3886 onScrollTouch(downEvent); 3887 downEvent.recycle(); 3888 } 3889 3890 @Override 3891 @ShadeViewRefactor(RefactorComponent.INPUT) 3892 public boolean onGenericMotionEvent(MotionEvent event) { 3893 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification 3894 || mDisallowScrollingInThisMotion) { 3895 return false; 3896 } 3897 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 3898 switch (event.getAction()) { 3899 case MotionEvent.ACTION_SCROLL: { 3900 if (!mIsBeingDragged) { 3901 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 3902 if (vscroll != 0) { 3903 final int delta = (int) (vscroll * getVerticalScrollFactor()); 3904 if (ANCHOR_SCROLLING) { 3905 mScrollAnchorViewY -= delta; 3906 updateScrollAnchor(); 3907 clampScrollPosition(); 3908 updateOnScrollChange(); 3909 } else { 3910 final int range = getScrollRange(); 3911 int oldScrollY = mOwnScrollY; 3912 int newScrollY = oldScrollY - delta; 3913 if (newScrollY < 0) { 3914 newScrollY = 0; 3915 } else if (newScrollY > range) { 3916 newScrollY = range; 3917 } 3918 if (newScrollY != oldScrollY) { 3919 setOwnScrollY(newScrollY); 3920 return true; 3921 } 3922 } 3923 } 3924 } 3925 } 3926 } 3927 } 3928 return super.onGenericMotionEvent(event); 3929 } 3930 3931 @ShadeViewRefactor(RefactorComponent.INPUT) 3932 private boolean onScrollTouch(MotionEvent ev) { 3933 if (!isScrollingEnabled()) { 3934 return false; 3935 } 3936 if (isInsideQsContainer(ev) && !mIsBeingDragged) { 3937 return false; 3938 } 3939 mForcedScroll = null; 3940 initVelocityTrackerIfNotExists(); 3941 mVelocityTracker.addMovement(ev); 3942 3943 final int action = ev.getActionMasked(); 3944 if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) { 3945 // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new 3946 // one starts. 3947 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent " 3948 + MotionEvent.actionToString(ev.getActionMasked())); 3949 return true; 3950 } 3951 3952 switch (action) { 3953 case MotionEvent.ACTION_DOWN: { 3954 if (getChildCount() == 0 || !isInContentBounds(ev)) { 3955 return false; 3956 } 3957 boolean isBeingDragged = !mScroller.isFinished(); 3958 setIsBeingDragged(isBeingDragged); 3959 /* 3960 * If being flinged and user touches, stop the fling. isFinished 3961 * will be false if being flinged. 3962 */ 3963 if (!mScroller.isFinished()) { 3964 mScroller.forceFinished(true); 3965 } 3966 3967 // Remember where the motion event started 3968 mLastMotionY = (int) ev.getY(); 3969 mDownX = (int) ev.getX(); 3970 mActivePointerId = ev.getPointerId(0); 3971 break; 3972 } 3973 case MotionEvent.ACTION_MOVE: 3974 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 3975 if (activePointerIndex == -1) { 3976 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 3977 break; 3978 } 3979 3980 final int y = (int) ev.getY(activePointerIndex); 3981 final int x = (int) ev.getX(activePointerIndex); 3982 int deltaY = mLastMotionY - y; 3983 final int xDiff = Math.abs(x - mDownX); 3984 final int yDiff = Math.abs(deltaY); 3985 final float touchSlop = getTouchSlop(ev); 3986 if (!mIsBeingDragged && yDiff > touchSlop && yDiff > xDiff) { 3987 setIsBeingDragged(true); 3988 if (deltaY > 0) { 3989 deltaY -= touchSlop; 3990 } else { 3991 deltaY += touchSlop; 3992 } 3993 } 3994 if (mIsBeingDragged) { 3995 // Scroll to follow the motion event 3996 mLastMotionY = y; 3997 float scrollAmount; 3998 int range; 3999 if (ANCHOR_SCROLLING) { 4000 range = 0; // unused in the methods it's being passed to 4001 } else { 4002 range = getScrollRange(); 4003 if (mExpandedInThisMotion) { 4004 range = Math.min(range, mMaxScrollAfterExpand); 4005 } 4006 } 4007 if (deltaY < 0) { 4008 scrollAmount = overScrollDown(deltaY); 4009 } else { 4010 scrollAmount = overScrollUp(deltaY, range); 4011 } 4012 4013 // Calling customOverScrollBy will call onCustomOverScrolled, which 4014 // sets the scrolling if applicable. 4015 if (scrollAmount != 0.0f) { 4016 // The scrolling motion could not be compensated with the 4017 // existing overScroll, we have to scroll the view 4018 customOverScrollBy((int) scrollAmount, mOwnScrollY, 4019 range, getHeight() / 2); 4020 // If we're scrolling, leavebehinds should be dismissed 4021 checkSnoozeLeavebehind(); 4022 } 4023 } 4024 break; 4025 case MotionEvent.ACTION_UP: 4026 if (mIsBeingDragged) { 4027 final VelocityTracker velocityTracker = mVelocityTracker; 4028 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 4029 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 4030 4031 if (shouldOverScrollFling(initialVelocity)) { 4032 onOverScrollFling(true, initialVelocity); 4033 } else { 4034 if (getChildCount() > 0) { 4035 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 4036 float currentOverScrollTop = getCurrentOverScrollAmount(true); 4037 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 4038 fling(-initialVelocity); 4039 } else { 4040 onOverScrollFling(false, initialVelocity); 4041 } 4042 } else { 4043 if (ANCHOR_SCROLLING) { 4044 // TODO 4045 } else { 4046 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 4047 getScrollRange())) { 4048 animateScroll(); 4049 } 4050 } 4051 } 4052 } 4053 } 4054 mActivePointerId = INVALID_POINTER; 4055 endDrag(); 4056 } 4057 4058 break; 4059 case MotionEvent.ACTION_CANCEL: 4060 if (mIsBeingDragged && getChildCount() > 0) { 4061 if (ANCHOR_SCROLLING) { 4062 // TODO 4063 } else { 4064 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 4065 getScrollRange())) { 4066 animateScroll(); 4067 } 4068 } 4069 mActivePointerId = INVALID_POINTER; 4070 endDrag(); 4071 } 4072 break; 4073 case MotionEvent.ACTION_POINTER_DOWN: { 4074 final int index = ev.getActionIndex(); 4075 mLastMotionY = (int) ev.getY(index); 4076 mDownX = (int) ev.getX(index); 4077 mActivePointerId = ev.getPointerId(index); 4078 break; 4079 } 4080 case MotionEvent.ACTION_POINTER_UP: 4081 onSecondaryPointerUp(ev); 4082 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 4083 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 4084 break; 4085 } 4086 return true; 4087 } 4088 4089 @ShadeViewRefactor(RefactorComponent.INPUT) 4090 protected boolean isInsideQsContainer(MotionEvent ev) { 4091 return ev.getY() < mQsContainer.getBottom(); 4092 } 4093 4094 @ShadeViewRefactor(RefactorComponent.INPUT) 4095 private void onOverScrollFling(boolean open, int initialVelocity) { 4096 if (mOverscrollTopChangedListener != null) { 4097 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 4098 } 4099 mDontReportNextOverScroll = true; 4100 setOverScrollAmount(0.0f, true, false); 4101 } 4102 4103 4104 @ShadeViewRefactor(RefactorComponent.INPUT) 4105 private void onSecondaryPointerUp(MotionEvent ev) { 4106 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 4107 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 4108 final int pointerId = ev.getPointerId(pointerIndex); 4109 if (pointerId == mActivePointerId) { 4110 // This was our active pointer going up. Choose a new 4111 // active pointer and adjust accordingly. 4112 // TODO: Make this decision more intelligent. 4113 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 4114 mLastMotionY = (int) ev.getY(newPointerIndex); 4115 mActivePointerId = ev.getPointerId(newPointerIndex); 4116 if (mVelocityTracker != null) { 4117 mVelocityTracker.clear(); 4118 } 4119 } 4120 } 4121 4122 @ShadeViewRefactor(RefactorComponent.INPUT) 4123 private void endDrag() { 4124 setIsBeingDragged(false); 4125 4126 recycleVelocityTracker(); 4127 4128 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 4129 setOverScrollAmount(0, true /* onTop */, true /* animate */); 4130 } 4131 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 4132 setOverScrollAmount(0, false /* onTop */, true /* animate */); 4133 } 4134 } 4135 4136 @Override 4137 @ShadeViewRefactor(RefactorComponent.INPUT) 4138 public boolean onInterceptTouchEvent(MotionEvent ev) { 4139 initDownStates(ev); 4140 handleEmptySpaceClick(ev); 4141 4142 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 4143 boolean expandWantsIt = false; 4144 boolean swipingInProgress = mSwipingInProgress; 4145 if (!swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) { 4146 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); 4147 } 4148 boolean scrollWantsIt = false; 4149 if (!swipingInProgress && !mExpandingNotification) { 4150 scrollWantsIt = onInterceptTouchEventScroll(ev); 4151 } 4152 boolean swipeWantsIt = false; 4153 if (!mIsBeingDragged 4154 && !mExpandingNotification 4155 && !mExpandedInThisMotion 4156 && !mOnlyScrollingInThisMotion 4157 && !mDisallowDismissInThisMotion) { 4158 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 4159 } 4160 // Check if we need to clear any snooze leavebehinds 4161 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 4162 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && 4163 !expandWantsIt && !scrollWantsIt) { 4164 mCheckForLeavebehind = false; 4165 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 4166 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 4167 false /* resetMenu */); 4168 } 4169 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 4170 mCheckForLeavebehind = true; 4171 } 4172 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); 4173 } 4174 4175 @ShadeViewRefactor(RefactorComponent.INPUT) 4176 private void handleEmptySpaceClick(MotionEvent ev) { 4177 switch (ev.getActionMasked()) { 4178 case MotionEvent.ACTION_MOVE: 4179 final float touchSlop = getTouchSlop(ev); 4180 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > touchSlop 4181 || Math.abs(ev.getX() - mInitialTouchX) > touchSlop)) { 4182 mTouchIsClick = false; 4183 } 4184 break; 4185 case MotionEvent.ACTION_UP: 4186 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && 4187 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { 4188 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); 4189 } 4190 break; 4191 } 4192 } 4193 4194 @ShadeViewRefactor(RefactorComponent.INPUT) 4195 private void initDownStates(MotionEvent ev) { 4196 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 4197 mExpandedInThisMotion = false; 4198 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 4199 mDisallowScrollingInThisMotion = false; 4200 mDisallowDismissInThisMotion = false; 4201 mTouchIsClick = true; 4202 mInitialTouchX = ev.getX(); 4203 mInitialTouchY = ev.getY(); 4204 } 4205 } 4206 4207 @Override 4208 @ShadeViewRefactor(RefactorComponent.INPUT) 4209 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 4210 super.requestDisallowInterceptTouchEvent(disallowIntercept); 4211 if (disallowIntercept) { 4212 cancelLongPress(); 4213 } 4214 } 4215 4216 @ShadeViewRefactor(RefactorComponent.INPUT) 4217 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 4218 if (!isScrollingEnabled()) { 4219 return false; 4220 } 4221 /* 4222 * This method JUST determines whether we want to intercept the motion. 4223 * If we return true, onMotionEvent will be called and we do the actual 4224 * scrolling there. 4225 */ 4226 4227 /* 4228 * Shortcut the most recurring case: the user is in the dragging 4229 * state and is moving their finger. We want to intercept this 4230 * motion. 4231 */ 4232 final int action = ev.getAction(); 4233 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 4234 return true; 4235 } 4236 4237 switch (action & MotionEvent.ACTION_MASK) { 4238 case MotionEvent.ACTION_MOVE: { 4239 /* 4240 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 4241 * whether the user has moved far enough from the original down touch. 4242 */ 4243 4244 /* 4245 * Locally do absolute value. mLastMotionY is set to the y value 4246 * of the down event. 4247 */ 4248 final int activePointerId = mActivePointerId; 4249 if (activePointerId == INVALID_POINTER) { 4250 // If we don't have a valid id, the touch down wasn't on content. 4251 break; 4252 } 4253 4254 final int pointerIndex = ev.findPointerIndex(activePointerId); 4255 if (pointerIndex == -1) { 4256 Log.e(TAG, "Invalid pointerId=" + activePointerId 4257 + " in onInterceptTouchEvent"); 4258 break; 4259 } 4260 4261 final int y = (int) ev.getY(pointerIndex); 4262 final int x = (int) ev.getX(pointerIndex); 4263 final int yDiff = Math.abs(y - mLastMotionY); 4264 final int xDiff = Math.abs(x - mDownX); 4265 if (yDiff > getTouchSlop(ev) && yDiff > xDiff) { 4266 setIsBeingDragged(true); 4267 mLastMotionY = y; 4268 mDownX = x; 4269 initVelocityTrackerIfNotExists(); 4270 mVelocityTracker.addMovement(ev); 4271 } 4272 break; 4273 } 4274 4275 case MotionEvent.ACTION_DOWN: { 4276 final int y = (int) ev.getY(); 4277 mScrolledToTopOnFirstDown = isScrolledToTop(); 4278 final ExpandableView childAtTouchPos = getChildAtPosition( 4279 ev.getX(), y, false /* requireMinHeight */, false /* ignoreDecors */); 4280 if (childAtTouchPos == null) { 4281 setIsBeingDragged(false); 4282 recycleVelocityTracker(); 4283 break; 4284 } 4285 4286 /* 4287 * Remember location of down touch. 4288 * ACTION_DOWN always refers to pointer index 0. 4289 */ 4290 mLastMotionY = y; 4291 mDownX = (int) ev.getX(); 4292 mActivePointerId = ev.getPointerId(0); 4293 4294 initOrResetVelocityTracker(); 4295 mVelocityTracker.addMovement(ev); 4296 /* 4297 * If being flinged and user touches the screen, initiate drag; 4298 * otherwise don't. mScroller.isFinished should be false when 4299 * being flinged. 4300 */ 4301 boolean isBeingDragged = !mScroller.isFinished(); 4302 setIsBeingDragged(isBeingDragged); 4303 break; 4304 } 4305 4306 case MotionEvent.ACTION_CANCEL: 4307 case MotionEvent.ACTION_UP: 4308 /* Release the drag */ 4309 setIsBeingDragged(false); 4310 mActivePointerId = INVALID_POINTER; 4311 recycleVelocityTracker(); 4312 if (ANCHOR_SCROLLING) { 4313 // TODO 4314 } else { 4315 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 4316 animateScroll(); 4317 } 4318 } 4319 break; 4320 case MotionEvent.ACTION_POINTER_UP: 4321 onSecondaryPointerUp(ev); 4322 break; 4323 } 4324 4325 /* 4326 * The only time we want to intercept motion events is if we are in the 4327 * drag mode. 4328 */ 4329 return mIsBeingDragged; 4330 } 4331 4332 /** 4333 * @return Whether the specified motion event is actually happening over the content. 4334 */ 4335 @ShadeViewRefactor(RefactorComponent.INPUT) 4336 private boolean isInContentBounds(MotionEvent event) { 4337 return isInContentBounds(event.getY()); 4338 } 4339 4340 4341 @VisibleForTesting 4342 @ShadeViewRefactor(RefactorComponent.INPUT) 4343 void setIsBeingDragged(boolean isDragged) { 4344 mIsBeingDragged = isDragged; 4345 if (isDragged) { 4346 requestDisallowInterceptTouchEvent(true); 4347 cancelLongPress(); 4348 resetExposedMenuView(true /* animate */, true /* force */); 4349 } 4350 } 4351 4352 @ShadeViewRefactor(RefactorComponent.INPUT) 4353 public void requestDisallowLongPress() { 4354 cancelLongPress(); 4355 } 4356 4357 @ShadeViewRefactor(RefactorComponent.INPUT) 4358 public void requestDisallowDismiss() { 4359 mDisallowDismissInThisMotion = true; 4360 } 4361 4362 @ShadeViewRefactor(RefactorComponent.INPUT) 4363 public void cancelLongPress() { 4364 mSwipeHelper.cancelLongPress(); 4365 } 4366 4367 @ShadeViewRefactor(RefactorComponent.INPUT) 4368 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 4369 mOnEmptySpaceClickListener = listener; 4370 } 4371 4372 /** @hide */ 4373 @Override 4374 @ShadeViewRefactor(RefactorComponent.INPUT) 4375 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 4376 if (super.performAccessibilityActionInternal(action, arguments)) { 4377 return true; 4378 } 4379 if (!isEnabled()) { 4380 return false; 4381 } 4382 int direction = -1; 4383 switch (action) { 4384 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 4385 // fall through 4386 case android.R.id.accessibilityActionScrollDown: 4387 direction = 1; 4388 // fall through 4389 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 4390 // fall through 4391 case android.R.id.accessibilityActionScrollUp: 4392 if (ANCHOR_SCROLLING) { 4393 // TODO 4394 } else { 4395 final int viewportHeight = 4396 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop 4397 - mShelf.getIntrinsicHeight(); 4398 final int targetScrollY = Math.max(0, 4399 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); 4400 if (targetScrollY != mOwnScrollY) { 4401 mScroller.startScroll(mScrollX, mOwnScrollY, 0, 4402 targetScrollY - mOwnScrollY); 4403 animateScroll(); 4404 return true; 4405 } 4406 } 4407 break; 4408 } 4409 return false; 4410 } 4411 4412 @ShadeViewRefactor(RefactorComponent.INPUT) 4413 public void closeControlsIfOutsideTouch(MotionEvent ev) { 4414 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 4415 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); 4416 View translatingParentView = mSwipeHelper.getTranslatingParentView(); 4417 View view = null; 4418 if (guts != null && !guts.getGutsContent().isLeavebehind()) { 4419 // Only close visible guts if they're not a leavebehind. 4420 view = guts; 4421 } else if (menuRow != null && menuRow.isMenuVisible() 4422 && translatingParentView != null) { 4423 // Checking menu 4424 view = translatingParentView; 4425 } 4426 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { 4427 // Touch was outside visible guts / menu notification, close what's visible 4428 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, 4429 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, 4430 false /* resetMenu */); 4431 resetExposedMenuView(true /* animate */, true /* force */); 4432 } 4433 } 4434 4435 @ShadeViewRefactor(RefactorComponent.INPUT) 4436 private void setSwipingInProgress(boolean swiping) { 4437 mSwipingInProgress = swiping; 4438 if (swiping) { 4439 requestDisallowInterceptTouchEvent(true); 4440 } 4441 } 4442 4443 @Override 4444 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4445 public void onWindowFocusChanged(boolean hasWindowFocus) { 4446 super.onWindowFocusChanged(hasWindowFocus); 4447 if (!hasWindowFocus) { 4448 cancelLongPress(); 4449 } 4450 } 4451 4452 @Override 4453 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4454 public void clearChildFocus(View child) { 4455 super.clearChildFocus(child); 4456 if (mForcedScroll == child) { 4457 mForcedScroll = null; 4458 } 4459 } 4460 4461 @Override 4462 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4463 public boolean isScrolledToTop() { 4464 if (ANCHOR_SCROLLING) { 4465 updateScrollAnchor(); 4466 // TODO: once we're recycling this will need to check the adapter position of the child 4467 return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0; 4468 } else { 4469 return mOwnScrollY == 0; 4470 } 4471 } 4472 4473 @Override 4474 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4475 public boolean isScrolledToBottom() { 4476 if (ANCHOR_SCROLLING) { 4477 return getMaxPositiveScrollAmount() <= 0; 4478 } else { 4479 return mOwnScrollY >= getScrollRange(); 4480 } 4481 } 4482 4483 @Override 4484 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4485 public View getHostView() { 4486 return this; 4487 } 4488 4489 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4490 public int getEmptyBottomMargin() { 4491 return Math.max(mMaxLayoutHeight - mContentHeight, 0); 4492 } 4493 4494 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4495 public void checkSnoozeLeavebehind() { 4496 if (mCheckForLeavebehind) { 4497 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 4498 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 4499 false /* resetMenu */); 4500 mCheckForLeavebehind = false; 4501 } 4502 } 4503 4504 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4505 public void resetCheckSnoozeLeavebehind() { 4506 mCheckForLeavebehind = true; 4507 } 4508 4509 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4510 public void onExpansionStarted() { 4511 mIsExpansionChanging = true; 4512 mAmbientState.setExpansionChanging(true); 4513 checkSnoozeLeavebehind(); 4514 } 4515 4516 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4517 public void onExpansionStopped() { 4518 mIsExpansionChanging = false; 4519 resetCheckSnoozeLeavebehind(); 4520 mAmbientState.setExpansionChanging(false); 4521 if (!mIsExpanded) { 4522 resetScrollPosition(); 4523 mStatusBar.resetUserExpandedStates(); 4524 clearTemporaryViews(); 4525 clearUserLockedViews(); 4526 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews(); 4527 if (draggedViews.size() > 0) { 4528 draggedViews.clear(); 4529 updateContinuousShadowDrawing(); 4530 } 4531 } 4532 } 4533 4534 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4535 private void clearUserLockedViews() { 4536 for (int i = 0; i < getChildCount(); i++) { 4537 ExpandableView child = (ExpandableView) getChildAt(i); 4538 if (child instanceof ExpandableNotificationRow) { 4539 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 4540 row.setUserLocked(false); 4541 } 4542 } 4543 } 4544 4545 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4546 private void clearTemporaryViews() { 4547 // lets make sure nothing is transient anymore 4548 clearTemporaryViewsInGroup(this); 4549 for (int i = 0; i < getChildCount(); i++) { 4550 ExpandableView child = (ExpandableView) getChildAt(i); 4551 if (child instanceof ExpandableNotificationRow) { 4552 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 4553 clearTemporaryViewsInGroup(row.getChildrenContainer()); 4554 } 4555 } 4556 } 4557 4558 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4559 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) { 4560 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { 4561 viewGroup.removeTransientView(viewGroup.getTransientView(0)); 4562 } 4563 } 4564 4565 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4566 public void onPanelTrackingStarted() { 4567 mPanelTracking = true; 4568 mAmbientState.setPanelTracking(true); 4569 resetExposedMenuView(true /* animate */, true /* force */); 4570 } 4571 4572 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4573 public void onPanelTrackingStopped() { 4574 mPanelTracking = false; 4575 mAmbientState.setPanelTracking(false); 4576 } 4577 4578 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4579 public void resetScrollPosition() { 4580 mScroller.abortAnimation(); 4581 if (ANCHOR_SCROLLING) { 4582 // TODO: once we're recycling this will need to modify the adapter position instead 4583 mScrollAnchorView = getFirstChildNotGone(); 4584 mScrollAnchorViewY = 0; 4585 updateOnScrollChange(); 4586 } else { 4587 setOwnScrollY(0); 4588 } 4589 } 4590 4591 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4592 private void setIsExpanded(boolean isExpanded) { 4593 boolean changed = isExpanded != mIsExpanded; 4594 mIsExpanded = isExpanded; 4595 mStackScrollAlgorithm.setIsExpanded(isExpanded); 4596 mAmbientState.setShadeExpanded(isExpanded); 4597 mStateAnimator.setShadeExpanded(isExpanded); 4598 mSwipeHelper.setIsExpanded(isExpanded); 4599 if (changed) { 4600 mWillExpand = false; 4601 if (!mIsExpanded) { 4602 mGroupManager.collapseAllGroups(); 4603 mExpandHelper.cancelImmediately(); 4604 } 4605 updateNotificationAnimationStates(); 4606 updateChronometers(); 4607 requestChildrenUpdate(); 4608 } 4609 } 4610 4611 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4612 private void updateChronometers() { 4613 int childCount = getChildCount(); 4614 for (int i = 0; i < childCount; i++) { 4615 updateChronometerForChild(getChildAt(i)); 4616 } 4617 } 4618 4619 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4620 private void updateChronometerForChild(View child) { 4621 if (child instanceof ExpandableNotificationRow) { 4622 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 4623 row.setChronometerRunning(mIsExpanded); 4624 } 4625 } 4626 4627 @Override 4628 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 4629 updateContentHeight(); 4630 updateScrollPositionOnExpandInBottom(view); 4631 clampScrollPosition(); 4632 notifyHeightChangeListener(view, needsAnimation); 4633 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow 4634 ? (ExpandableNotificationRow) view 4635 : null; 4636 NotificationSection firstSection = getFirstVisibleSection(); 4637 ExpandableView firstVisibleChild = 4638 firstSection == null ? null : firstSection.getFirstVisibleChild(); 4639 if (row != null) { 4640 if (row == firstVisibleChild 4641 || row.getNotificationParent() == firstVisibleChild) { 4642 updateAlgorithmLayoutMinHeight(); 4643 } 4644 } 4645 if (needsAnimation) { 4646 requestAnimationOnViewResize(row); 4647 } 4648 requestChildrenUpdate(); 4649 } 4650 4651 @Override 4652 public void onReset(ExpandableView view) { 4653 updateAnimationState(view); 4654 updateChronometerForChild(view); 4655 } 4656 4657 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4658 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 4659 if (view instanceof ExpandableNotificationRow && !onKeyguard()) { 4660 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 4661 // TODO: once we're recycling this will need to check the adapter position of the child 4662 if (row.isUserLocked() && row != getFirstChildNotGone()) { 4663 if (row.isSummaryWithChildren()) { 4664 return; 4665 } 4666 // We are actually expanding this view 4667 float endPosition = row.getTranslationY() + row.getActualHeight(); 4668 if (row.isChildInGroup()) { 4669 endPosition += row.getNotificationParent().getTranslationY(); 4670 } 4671 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation; 4672 NotificationSection lastSection = getLastVisibleSection(); 4673 ExpandableView lastVisibleChild = 4674 lastSection == null ? null : lastSection.getLastVisibleChild(); 4675 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) { 4676 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; 4677 } 4678 if (endPosition > layoutEnd) { 4679 if (ANCHOR_SCROLLING) { 4680 mScrollAnchorViewY -= (endPosition - layoutEnd); 4681 updateScrollAnchor(); 4682 updateOnScrollChange(); 4683 } else { 4684 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); 4685 } 4686 mDisallowScrollingInThisMotion = true; 4687 } 4688 } 4689 } 4690 } 4691 4692 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4693 public void setOnHeightChangedListener( 4694 ExpandableView.OnHeightChangedListener onHeightChangedListener) { 4695 this.mOnHeightChangedListener = onHeightChangedListener; 4696 } 4697 4698 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4699 public void onChildAnimationFinished() { 4700 setAnimationRunning(false); 4701 requestChildrenUpdate(); 4702 runAnimationFinishedRunnables(); 4703 clearTransient(); 4704 clearHeadsUpDisappearRunning(); 4705 } 4706 4707 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4708 private void clearHeadsUpDisappearRunning() { 4709 for (int i = 0; i < getChildCount(); i++) { 4710 View view = getChildAt(i); 4711 if (view instanceof ExpandableNotificationRow) { 4712 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 4713 row.setHeadsUpAnimatingAway(false); 4714 if (row.isSummaryWithChildren()) { 4715 for (ExpandableNotificationRow child : row.getAttachedChildren()) { 4716 child.setHeadsUpAnimatingAway(false); 4717 } 4718 } 4719 } 4720 } 4721 } 4722 4723 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4724 private void clearTransient() { 4725 for (ExpandableView view : mClearTransientViewsWhenFinished) { 4726 StackStateAnimator.removeTransientView(view); 4727 } 4728 mClearTransientViewsWhenFinished.clear(); 4729 } 4730 4731 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4732 private void runAnimationFinishedRunnables() { 4733 for (Runnable runnable : mAnimationFinishedRunnables) { 4734 runnable.run(); 4735 } 4736 mAnimationFinishedRunnables.clear(); 4737 } 4738 4739 /** 4740 * See {@link AmbientState#setDimmed}. 4741 */ 4742 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4743 public void setDimmed(boolean dimmed, boolean animate) { 4744 dimmed &= onKeyguard(); 4745 mAmbientState.setDimmed(dimmed); 4746 if (animate && mAnimationsEnabled) { 4747 mDimmedNeedsAnimation = true; 4748 mNeedsAnimation = true; 4749 animateDimmed(dimmed); 4750 } else { 4751 setDimAmount(dimmed ? 1.0f : 0.0f); 4752 } 4753 requestChildrenUpdate(); 4754 } 4755 4756 @VisibleForTesting 4757 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4758 boolean isDimmed() { 4759 return mAmbientState.isDimmed(); 4760 } 4761 4762 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4763 private void setDimAmount(float dimAmount) { 4764 mDimAmount = dimAmount; 4765 updateBackgroundDimming(); 4766 } 4767 4768 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4769 private void animateDimmed(boolean dimmed) { 4770 if (mDimAnimator != null) { 4771 mDimAnimator.cancel(); 4772 } 4773 float target = dimmed ? 1.0f : 0.0f; 4774 if (target == mDimAmount) { 4775 return; 4776 } 4777 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target); 4778 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED); 4779 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 4780 mDimAnimator.addListener(mDimEndListener); 4781 mDimAnimator.addUpdateListener(mDimUpdateListener); 4782 mDimAnimator.start(); 4783 } 4784 4785 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4786 private void updateSensitiveness(boolean animate) { 4787 boolean hideSensitive = mLockscreenUserManager.isAnyProfilePublicMode(); 4788 if (hideSensitive != mAmbientState.isHideSensitive()) { 4789 int childCount = getChildCount(); 4790 for (int i = 0; i < childCount; i++) { 4791 ExpandableView v = (ExpandableView) getChildAt(i); 4792 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 4793 } 4794 mAmbientState.setHideSensitive(hideSensitive); 4795 if (animate && mAnimationsEnabled) { 4796 mHideSensitiveNeedsAnimation = true; 4797 mNeedsAnimation = true; 4798 } 4799 updateContentHeight(); 4800 requestChildrenUpdate(); 4801 } 4802 } 4803 4804 /** 4805 * See {@link AmbientState#setActivatedChild}. 4806 */ 4807 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4808 public void setActivatedChild(ActivatableNotificationView activatedChild) { 4809 mAmbientState.setActivatedChild(activatedChild); 4810 if (mAnimationsEnabled) { 4811 mActivateNeedsAnimation = true; 4812 mNeedsAnimation = true; 4813 } 4814 requestChildrenUpdate(); 4815 } 4816 4817 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4818 public ActivatableNotificationView getActivatedChild() { 4819 return mAmbientState.getActivatedChild(); 4820 } 4821 4822 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4823 private void applyCurrentState() { 4824 int numChildren = getChildCount(); 4825 for (int i = 0; i < numChildren; i++) { 4826 ExpandableView child = (ExpandableView) getChildAt(i); 4827 child.applyViewState(); 4828 } 4829 4830 if (mListener != null) { 4831 mListener.onChildLocationsChanged(); 4832 } 4833 runAnimationFinishedRunnables(); 4834 setAnimationRunning(false); 4835 updateBackground(); 4836 updateViewShadows(); 4837 updateClippingToTopRoundedCorner(); 4838 } 4839 4840 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4841 private void updateViewShadows() { 4842 // we need to work around an issue where the shadow would not cast between siblings when 4843 // their z difference is between 0 and 0.1 4844 4845 // Lefts first sort by Z difference 4846 for (int i = 0; i < getChildCount(); i++) { 4847 ExpandableView child = (ExpandableView) getChildAt(i); 4848 if (child.getVisibility() != GONE) { 4849 mTmpSortedChildren.add(child); 4850 } 4851 } 4852 Collections.sort(mTmpSortedChildren, mViewPositionComparator); 4853 4854 // Now lets update the shadow for the views 4855 ExpandableView previous = null; 4856 for (int i = 0; i < mTmpSortedChildren.size(); i++) { 4857 ExpandableView expandableView = mTmpSortedChildren.get(i); 4858 float translationZ = expandableView.getTranslationZ(); 4859 float otherZ = previous == null ? translationZ : previous.getTranslationZ(); 4860 float diff = otherZ - translationZ; 4861 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) { 4862 // There is no fake shadow to be drawn 4863 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 4864 } else { 4865 float yLocation = previous.getTranslationY() + previous.getActualHeight() - 4866 expandableView.getTranslationY() - previous.getExtraBottomPadding(); 4867 expandableView.setFakeShadowIntensity( 4868 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, 4869 previous.getOutlineAlpha(), (int) yLocation, 4870 previous.getOutlineTranslation()); 4871 } 4872 previous = expandableView; 4873 } 4874 4875 mTmpSortedChildren.clear(); 4876 } 4877 4878 /** 4879 * Update colors of "dismiss" and "empty shade" views. 4880 * 4881 * @param lightTheme True if light theme should be used. 4882 */ 4883 @ShadeViewRefactor(RefactorComponent.DECORATOR) 4884 public void updateDecorViews(boolean lightTheme) { 4885 if (lightTheme == mUsingLightTheme) { 4886 return; 4887 } 4888 mUsingLightTheme = lightTheme; 4889 Context context = new ContextThemeWrapper(mContext, 4890 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI); 4891 final @ColorInt int textColor = 4892 Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor); 4893 mSectionsManager.setHeaderForegroundColor(textColor); 4894 mFooterView.setTextColor(textColor); 4895 mEmptyShadeView.setTextColor(textColor); 4896 } 4897 4898 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4899 public void goToFullShade(long delay) { 4900 mGoToFullShadeNeedsAnimation = true; 4901 mGoToFullShadeDelay = delay; 4902 mNeedsAnimation = true; 4903 requestChildrenUpdate(); 4904 } 4905 4906 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4907 public void cancelExpandHelper() { 4908 mExpandHelper.cancel(); 4909 } 4910 4911 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4912 public void setIntrinsicPadding(int intrinsicPadding) { 4913 mIntrinsicPadding = intrinsicPadding; 4914 mAmbientState.setIntrinsicPadding(intrinsicPadding); 4915 } 4916 4917 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 4918 public int getIntrinsicPadding() { 4919 return mIntrinsicPadding; 4920 } 4921 4922 @Override 4923 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4924 public boolean shouldDelayChildPressedState() { 4925 return true; 4926 } 4927 4928 /** 4929 * See {@link AmbientState#setDozing}. 4930 */ 4931 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4932 public void setDozing(boolean dozing, boolean animate, 4933 @Nullable PointF touchWakeUpScreenLocation) { 4934 if (mAmbientState.isDozing() == dozing) { 4935 return; 4936 } 4937 mAmbientState.setDozing(dozing); 4938 requestChildrenUpdate(); 4939 notifyHeightChangeListener(mShelf); 4940 } 4941 4942 /** 4943 * Sets the current hide amount. 4944 * 4945 * @param linearHideAmount The hide amount that follows linear interpoloation in the 4946 * animation, 4947 * i.e. animates from 0 to 1 or vice-versa in a linear manner. 4948 * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the 4949 * animation curve. 4950 */ 4951 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4952 public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) { 4953 mLinearHideAmount = linearHideAmount; 4954 mInterpolatedHideAmount = interpolatedHideAmount; 4955 boolean wasFullyHidden = mAmbientState.isFullyHidden(); 4956 boolean wasHiddenAtAll = mAmbientState.isHiddenAtAll(); 4957 mAmbientState.setHideAmount(interpolatedHideAmount); 4958 boolean nowFullyHidden = mAmbientState.isFullyHidden(); 4959 boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll(); 4960 if (nowFullyHidden != wasFullyHidden) { 4961 updateVisibility(); 4962 } 4963 if (!wasHiddenAtAll && nowHiddenAtAll) { 4964 resetExposedMenuView(true /* animate */, true /* animate */); 4965 } 4966 if (nowFullyHidden != wasFullyHidden || wasHiddenAtAll != nowHiddenAtAll) { 4967 invalidateOutline(); 4968 } 4969 updateAlgorithmHeightAndPadding(); 4970 updateBackgroundDimming(); 4971 requestChildrenUpdate(); 4972 updateOwnTranslationZ(); 4973 } 4974 4975 private void updateOwnTranslationZ() { 4976 // Since we are clipping to the outline we need to make sure that the shadows aren't 4977 // clipped when pulsing 4978 float ownTranslationZ = 0; 4979 if (mKeyguardBypassController.getBypassEnabled() && mAmbientState.isHiddenAtAll()) { 4980 ExpandableView firstChildNotGone = getFirstChildNotGone(); 4981 if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) { 4982 ownTranslationZ = firstChildNotGone.getTranslationZ(); 4983 } 4984 } 4985 setTranslationZ(ownTranslationZ); 4986 } 4987 4988 private void updateVisibility() { 4989 boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard(); 4990 setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); 4991 } 4992 4993 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 4994 public void notifyHideAnimationStart(boolean hide) { 4995 // We only swap the scaling factor if we're fully hidden or fully awake to avoid 4996 // interpolation issues when playing with the power button. 4997 if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) { 4998 mBackgroundXFactor = hide ? 1.8f : 1.5f; 4999 mHideXInterpolator = hide 5000 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE 5001 : Interpolators.FAST_OUT_SLOW_IN; 5002 } 5003 } 5004 5005 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5006 private int getNotGoneIndex(View child) { 5007 int count = getChildCount(); 5008 int notGoneIndex = 0; 5009 for (int i = 0; i < count; i++) { 5010 View v = getChildAt(i); 5011 if (child == v) { 5012 return notGoneIndex; 5013 } 5014 if (v.getVisibility() != View.GONE) { 5015 notGoneIndex++; 5016 } 5017 } 5018 return -1; 5019 } 5020 5021 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5022 public void setFooterView(@NonNull FooterView footerView) { 5023 int index = -1; 5024 if (mFooterView != null) { 5025 index = indexOfChild(mFooterView); 5026 removeView(mFooterView); 5027 } 5028 mFooterView = footerView; 5029 addView(mFooterView, index); 5030 } 5031 5032 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5033 public void setEmptyShadeView(EmptyShadeView emptyShadeView) { 5034 int index = -1; 5035 if (mEmptyShadeView != null) { 5036 index = indexOfChild(mEmptyShadeView); 5037 removeView(mEmptyShadeView); 5038 } 5039 mEmptyShadeView = emptyShadeView; 5040 addView(mEmptyShadeView, index); 5041 } 5042 5043 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5044 public void updateEmptyShadeView(boolean visible) { 5045 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); 5046 5047 int oldTextRes = mEmptyShadeView.getTextResource(); 5048 int newTextRes = mZenController.areNotificationsHiddenInShade() 5049 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text; 5050 if (oldTextRes != newTextRes) { 5051 mEmptyShadeView.setText(newTextRes); 5052 } 5053 } 5054 5055 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5056 public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { 5057 if (mFooterView == null) { 5058 return; 5059 } 5060 boolean animate = mIsExpanded && mAnimationsEnabled; 5061 mFooterView.setVisible(visible, animate); 5062 mFooterView.setSecondaryVisible(showDismissView, animate); 5063 mFooterView.showHistory(showHistory); 5064 } 5065 5066 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5067 public void setDismissAllInProgress(boolean dismissAllInProgress) { 5068 mDismissAllInProgress = dismissAllInProgress; 5069 mAmbientState.setDismissAllInProgress(dismissAllInProgress); 5070 handleDismissAllClipping(); 5071 } 5072 5073 @ShadeViewRefactor(RefactorComponent.ADAPTER) 5074 private void handleDismissAllClipping() { 5075 final int count = getChildCount(); 5076 boolean previousChildWillBeDismissed = false; 5077 for (int i = 0; i < count; i++) { 5078 ExpandableView child = (ExpandableView) getChildAt(i); 5079 if (child.getVisibility() == GONE) { 5080 continue; 5081 } 5082 if (mDismissAllInProgress && previousChildWillBeDismissed) { 5083 child.setMinClipTopAmount(child.getClipTopAmount()); 5084 } else { 5085 child.setMinClipTopAmount(0); 5086 } 5087 previousChildWillBeDismissed = canChildBeDismissed(child); 5088 } 5089 } 5090 5091 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5092 public boolean isFooterViewNotGone() { 5093 return mFooterView != null 5094 && mFooterView.getVisibility() != View.GONE 5095 && !mFooterView.willBeGone(); 5096 } 5097 5098 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5099 public boolean isFooterViewContentVisible() { 5100 return mFooterView != null && mFooterView.isContentVisible(); 5101 } 5102 5103 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5104 public int getFooterViewHeightWithPadding() { 5105 return mFooterView == null ? 0 : mFooterView.getHeight() 5106 + mPaddingBetweenElements 5107 + mGapHeight; 5108 } 5109 5110 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5111 public int getEmptyShadeViewHeight() { 5112 return mEmptyShadeView.getHeight(); 5113 } 5114 5115 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5116 public float getBottomMostNotificationBottom() { 5117 final int count = getChildCount(); 5118 float max = 0; 5119 for (int childIdx = 0; childIdx < count; childIdx++) { 5120 ExpandableView child = (ExpandableView) getChildAt(childIdx); 5121 if (child.getVisibility() == GONE) { 5122 continue; 5123 } 5124 float bottom = child.getTranslationY() + child.getActualHeight() 5125 - child.getClipBottomAmount(); 5126 if (bottom > max) { 5127 max = bottom; 5128 } 5129 } 5130 return max + getStackTranslation(); 5131 } 5132 5133 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5134 public void setStatusBar(StatusBar statusBar) { 5135 this.mStatusBar = statusBar; 5136 } 5137 5138 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5139 public void setGroupManager(NotificationGroupManager groupManager) { 5140 this.mGroupManager = groupManager; 5141 mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener); 5142 } 5143 5144 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5145 private void requestAnimateEverything() { 5146 if (mIsExpanded && mAnimationsEnabled) { 5147 mEverythingNeedsAnimation = true; 5148 mNeedsAnimation = true; 5149 requestChildrenUpdate(); 5150 } 5151 } 5152 5153 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5154 public boolean isBelowLastNotification(float touchX, float touchY) { 5155 int childCount = getChildCount(); 5156 for (int i = childCount - 1; i >= 0; i--) { 5157 ExpandableView child = (ExpandableView) getChildAt(i); 5158 if (child.getVisibility() != View.GONE) { 5159 float childTop = child.getY(); 5160 if (childTop > touchY) { 5161 // we are above a notification entirely let's abort 5162 return false; 5163 } 5164 boolean belowChild = touchY > childTop + child.getActualHeight() 5165 - child.getClipBottomAmount(); 5166 if (child == mFooterView) { 5167 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(), 5168 touchY - childTop)) { 5169 // We clicked on the dismiss button 5170 return false; 5171 } 5172 } else if (child == mEmptyShadeView) { 5173 // We arrived at the empty shade view, for which we accept all clicks 5174 return true; 5175 } else if (!belowChild) { 5176 // We are on a child 5177 return false; 5178 } 5179 } 5180 } 5181 return touchY > mTopPadding + mStackTranslation; 5182 } 5183 5184 /** @hide */ 5185 @Override 5186 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5187 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 5188 super.onInitializeAccessibilityEventInternal(event); 5189 event.setScrollable(mScrollable); 5190 event.setMaxScrollX(mScrollX); 5191 if (ANCHOR_SCROLLING) { 5192 // TODO 5193 } else { 5194 event.setScrollY(mOwnScrollY); 5195 event.setMaxScrollY(getScrollRange()); 5196 } 5197 } 5198 5199 @Override 5200 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5201 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 5202 super.onInitializeAccessibilityNodeInfoInternal(info); 5203 if (mScrollable) { 5204 info.setScrollable(true); 5205 if (mBackwardScrollable) { 5206 info.addAction( 5207 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 5208 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); 5209 } 5210 if (mForwardScrollable) { 5211 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 5212 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN); 5213 } 5214 } 5215 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview 5216 info.setClassName(ScrollView.class.getName()); 5217 } 5218 5219 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5220 public void generateChildOrderChangedEvent() { 5221 if (mIsExpanded && mAnimationsEnabled) { 5222 mGenerateChildOrderChangedEvent = true; 5223 mNeedsAnimation = true; 5224 requestChildrenUpdate(); 5225 } 5226 } 5227 5228 @Override 5229 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5230 public int getContainerChildCount() { 5231 return getChildCount(); 5232 } 5233 5234 @Override 5235 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5236 public View getContainerChildAt(int i) { 5237 return getChildAt(i); 5238 } 5239 5240 @Override 5241 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5242 public void removeContainerView(View v) { 5243 Assert.isMainThread(); 5244 removeView(v); 5245 } 5246 5247 @Override 5248 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5249 public void removeListItem(NotificationListItem v) { 5250 removeContainerView(v.getView()); 5251 } 5252 5253 @Override 5254 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5255 public void addContainerView(View v) { 5256 Assert.isMainThread(); 5257 addView(v); 5258 } 5259 5260 @Override 5261 public void addListItem(NotificationListItem v) { 5262 addContainerView(v.getView()); 5263 } 5264 5265 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5266 public void runAfterAnimationFinished(Runnable runnable) { 5267 mAnimationFinishedRunnables.add(runnable); 5268 } 5269 5270 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { 5271 ExpandableNotificationRow row = entry.getHeadsUpAnimationView(); 5272 generateHeadsUpAnimation(row, isHeadsUp); 5273 } 5274 5275 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5276 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 5277 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) { 5278 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); 5279 mNeedsAnimation = true; 5280 if (!mIsExpanded && !mWillExpand && !isHeadsUp) { 5281 row.setHeadsUpAnimatingAway(true); 5282 } 5283 requestChildrenUpdate(); 5284 } 5285 } 5286 5287 /** 5288 * Set the boundary for the bottom heads up position. The heads up will always be above this 5289 * position. 5290 * 5291 * @param height the height of the screen 5292 * @param bottomBarHeight the height of the bar on the bottom 5293 */ 5294 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5295 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 5296 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); 5297 mStateAnimator.setHeadsUpAppearHeightBottom(height); 5298 requestChildrenUpdate(); 5299 } 5300 5301 @Override 5302 public void setWillExpand(boolean willExpand) { 5303 mWillExpand = willExpand; 5304 } 5305 5306 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5307 public void setTrackingHeadsUp(ExpandableNotificationRow row) { 5308 mAmbientState.setTrackedHeadsUpRow(row); 5309 mTrackingHeadsUp = row != null; 5310 mRoundnessManager.setTrackingHeadsUp(row); 5311 } 5312 5313 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5314 public void setScrimController(ScrimController scrimController) { 5315 mScrimController = scrimController; 5316 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming); 5317 } 5318 5319 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5320 public void forceNoOverlappingRendering(boolean force) { 5321 mForceNoOverlappingRendering = force; 5322 } 5323 5324 @Override 5325 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5326 public boolean hasOverlappingRendering() { 5327 return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); 5328 } 5329 5330 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5331 public void setAnimationRunning(boolean animationRunning) { 5332 if (animationRunning != mAnimationRunning) { 5333 if (animationRunning) { 5334 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater); 5335 } else { 5336 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater); 5337 } 5338 mAnimationRunning = animationRunning; 5339 updateContinuousShadowDrawing(); 5340 } 5341 } 5342 5343 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5344 public boolean isExpanded() { 5345 return mIsExpanded; 5346 } 5347 5348 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5349 public void setPulsing(boolean pulsing, boolean animated) { 5350 if (!mPulsing && !pulsing) { 5351 return; 5352 } 5353 mPulsing = pulsing; 5354 mAmbientState.setPulsing(pulsing); 5355 mSwipeHelper.setPulsing(pulsing); 5356 updateNotificationAnimationStates(); 5357 updateAlgorithmHeightAndPadding(); 5358 updateContentHeight(); 5359 requestChildrenUpdate(); 5360 notifyHeightChangeListener(null, animated); 5361 } 5362 5363 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5364 public void setQsExpanded(boolean qsExpanded) { 5365 mQsExpanded = qsExpanded; 5366 updateAlgorithmLayoutMinHeight(); 5367 updateScrollability(); 5368 } 5369 5370 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5371 public void setQsExpansionFraction(float qsExpansionFraction) { 5372 mQsExpansionFraction = qsExpansionFraction; 5373 } 5374 5375 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5376 private void setOwnScrollY(int ownScrollY) { 5377 assert !ANCHOR_SCROLLING; 5378 if (ownScrollY != mOwnScrollY) { 5379 // We still want to call the normal scrolled changed for accessibility reasons 5380 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); 5381 mOwnScrollY = ownScrollY; 5382 updateOnScrollChange(); 5383 } 5384 } 5385 5386 private void updateOnScrollChange() { 5387 updateForwardAndBackwardScrollability(); 5388 requestChildrenUpdate(); 5389 } 5390 5391 private void updateScrollAnchor() { 5392 int anchorIndex = indexOfChild(mScrollAnchorView); 5393 // If the anchor view has been scrolled off the top, move to the next view. 5394 while (mScrollAnchorViewY < 0) { 5395 View nextAnchor = null; 5396 for (int i = anchorIndex + 1; i < getChildCount(); i++) { 5397 View child = getChildAt(i); 5398 if (child.getVisibility() != View.GONE 5399 && child instanceof ExpandableNotificationRow) { 5400 anchorIndex = i; 5401 nextAnchor = child; 5402 break; 5403 } 5404 } 5405 if (nextAnchor == null) { 5406 break; 5407 } 5408 mScrollAnchorViewY += 5409 (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY()); 5410 mScrollAnchorView = nextAnchor; 5411 } 5412 // If the view above the anchor view is fully visible, make it the anchor view. 5413 while (anchorIndex > 0 && mScrollAnchorViewY > 0) { 5414 View prevAnchor = null; 5415 for (int i = anchorIndex - 1; i >= 0; i--) { 5416 View child = getChildAt(i); 5417 if (child.getVisibility() != View.GONE 5418 && child instanceof ExpandableNotificationRow) { 5419 anchorIndex = i; 5420 prevAnchor = child; 5421 break; 5422 } 5423 } 5424 if (prevAnchor == null) { 5425 break; 5426 } 5427 float distanceToPreviousAnchor = 5428 mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY(); 5429 if (distanceToPreviousAnchor < mScrollAnchorViewY) { 5430 mScrollAnchorViewY -= (int) distanceToPreviousAnchor; 5431 mScrollAnchorView = prevAnchor; 5432 } 5433 } 5434 } 5435 5436 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5437 public void setShelf(NotificationShelf shelf) { 5438 int index = -1; 5439 if (mShelf != null) { 5440 index = indexOfChild(mShelf); 5441 removeView(mShelf); 5442 } 5443 mShelf = shelf; 5444 addView(mShelf, index); 5445 mAmbientState.setShelf(shelf); 5446 mStateAnimator.setShelf(shelf); 5447 shelf.bind(mAmbientState, this); 5448 if (ANCHOR_SCROLLING) { 5449 mScrollAnchorView = mShelf; 5450 } 5451 } 5452 5453 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5454 public NotificationShelf getNotificationShelf() { 5455 return mShelf; 5456 } 5457 5458 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5459 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) { 5460 if (mMaxDisplayedNotifications != maxDisplayedNotifications) { 5461 mMaxDisplayedNotifications = maxDisplayedNotifications; 5462 updateContentHeight(); 5463 notifyHeightChangeListener(mShelf); 5464 } 5465 } 5466 5467 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5468 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) { 5469 mShouldShowShelfOnly = shouldShowShelfOnly; 5470 updateAlgorithmLayoutMinHeight(); 5471 } 5472 5473 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5474 public int getMinExpansionHeight() { 5475 return mShelf.getIntrinsicHeight() 5476 - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2 5477 + mWaterfallTopInset; 5478 } 5479 5480 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5481 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) { 5482 mInHeadsUpPinnedMode = inHeadsUpPinnedMode; 5483 updateClipping(); 5484 } 5485 5486 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5487 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 5488 mHeadsUpAnimatingAway = headsUpAnimatingAway; 5489 updateClipping(); 5490 } 5491 5492 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5493 @VisibleForTesting 5494 protected void setStatusBarState(int statusBarState) { 5495 mStatusBarState = statusBarState; 5496 mAmbientState.setStatusBarState(statusBarState); 5497 } 5498 5499 private void onStatePostChange() { 5500 boolean onKeyguard = onKeyguard(); 5501 5502 if (mHeadsUpAppearanceController != null) { 5503 mHeadsUpAppearanceController.onStateChanged(); 5504 } 5505 5506 SysuiStatusBarStateController state = (SysuiStatusBarStateController) 5507 Dependency.get(StatusBarStateController.class); 5508 updateSensitiveness(state.goingToFullShade() /* animate */); 5509 setDimmed(onKeyguard, state.fromShadeLocked() /* animate */); 5510 setExpandingEnabled(!onKeyguard); 5511 ActivatableNotificationView activatedChild = getActivatedChild(); 5512 setActivatedChild(null); 5513 if (activatedChild != null) { 5514 activatedChild.makeInactive(false /* animate */); 5515 } 5516 updateFooter(); 5517 requestChildrenUpdate(); 5518 onUpdateRowStates(); 5519 5520 mEntryManager.updateNotifications("StatusBar state changed"); 5521 updateVisibility(); 5522 } 5523 5524 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5525 public void setExpandingVelocity(float expandingVelocity) { 5526 mAmbientState.setExpandingVelocity(expandingVelocity); 5527 } 5528 5529 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 5530 public float getOpeningHeight() { 5531 if (mEmptyShadeView.getVisibility() == GONE) { 5532 return getMinExpansionHeight(); 5533 } else { 5534 return getAppearEndPosition(); 5535 } 5536 } 5537 5538 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5539 public void setIsFullWidth(boolean isFullWidth) { 5540 mAmbientState.setPanelFullWidth(isFullWidth); 5541 } 5542 5543 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5544 public void setUnlockHintRunning(boolean running) { 5545 mAmbientState.setUnlockHintRunning(running); 5546 } 5547 5548 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5549 public void setQsCustomizerShowing(boolean isShowing) { 5550 mAmbientState.setQsCustomizerShowing(isShowing); 5551 requestChildrenUpdate(); 5552 } 5553 5554 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5555 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) { 5556 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; 5557 } 5558 5559 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5560 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 5561 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" 5562 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s" 5563 + " qsExpandFraction=%f]", 5564 this.getClass().getSimpleName(), 5565 mPulsing ? "T" : "f", 5566 mAmbientState.isQsCustomizerShowing() ? "T" : "f", 5567 getVisibility() == View.VISIBLE ? "visible" 5568 : getVisibility() == View.GONE ? "gone" 5569 : "invisible", 5570 getAlpha(), 5571 mAmbientState.getScrollY(), 5572 mMaxTopPadding, 5573 mShouldShowShelfOnly ? "T" : "f", 5574 mQsExpansionFraction)); 5575 int childCount = getChildCount(); 5576 pw.println(" Number of children: " + childCount); 5577 pw.println(); 5578 5579 for (int i = 0; i < childCount; i++) { 5580 ExpandableView child = (ExpandableView) getChildAt(i); 5581 child.dump(fd, pw, args); 5582 if (!(child instanceof ExpandableNotificationRow)) { 5583 pw.println(" " + child.getClass().getSimpleName()); 5584 // Notifications dump it's viewstate as part of their dump to support children 5585 ExpandableViewState viewState = child.getViewState(); 5586 if (viewState == null) { 5587 pw.println(" no viewState!!!"); 5588 } else { 5589 pw.print(" "); 5590 viewState.dump(fd, pw, args); 5591 pw.println(); 5592 pw.println(); 5593 } 5594 } 5595 } 5596 int transientViewCount = getTransientViewCount(); 5597 pw.println(" Transient Views: " + transientViewCount); 5598 for (int i = 0; i < transientViewCount; i++) { 5599 ExpandableView child = (ExpandableView) getTransientView(i); 5600 child.dump(fd, pw, args); 5601 } 5602 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews(); 5603 int draggedCount = draggedViews.size(); 5604 pw.println(" Dragged Views: " + draggedCount); 5605 for (int i = 0; i < draggedCount; i++) { 5606 ExpandableView child = (ExpandableView) draggedViews.get(i); 5607 child.dump(fd, pw, args); 5608 } 5609 } 5610 5611 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5612 public boolean isFullyHidden() { 5613 return mAmbientState.isFullyHidden(); 5614 } 5615 5616 /** 5617 * Add a listener whenever the expanded height changes. The first value passed as an 5618 * argument is the expanded height and the second one is the appearFraction. 5619 * 5620 * @param listener the listener to notify. 5621 */ 5622 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5623 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 5624 mExpandedHeightListeners.add(listener); 5625 } 5626 5627 /** 5628 * Stop a listener from listening to the expandedHeight. 5629 */ 5630 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5631 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 5632 mExpandedHeightListeners.remove(listener); 5633 } 5634 5635 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5636 public void setHeadsUpAppearanceController( 5637 HeadsUpAppearanceController headsUpAppearanceController) { 5638 mHeadsUpAppearanceController = headsUpAppearanceController; 5639 } 5640 5641 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5642 public void setIconAreaController(NotificationIconAreaController controller) { 5643 mIconAreaController = controller; 5644 } 5645 5646 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5647 @VisibleForTesting 5648 void clearNotifications( 5649 @SelectedRows int selection, 5650 boolean closeShade) { 5651 // animate-swipe all dismissable notifications, then animate the shade closed 5652 int numChildren = getChildCount(); 5653 5654 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren); 5655 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren); 5656 for (int i = 0; i < numChildren; i++) { 5657 final View child = getChildAt(i); 5658 if (child instanceof ExpandableNotificationRow) { 5659 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 5660 boolean parentVisible = false; 5661 boolean hasClipBounds = child.getClipBounds(mTmpRect); 5662 if (includeChildInDismissAll(row, selection)) { 5663 viewsToRemove.add(row); 5664 if (child.getVisibility() == View.VISIBLE 5665 && (!hasClipBounds || mTmpRect.height() > 0)) { 5666 viewsToHide.add(child); 5667 parentVisible = true; 5668 } 5669 } else if (child.getVisibility() == View.VISIBLE 5670 && (!hasClipBounds || mTmpRect.height() > 0)) { 5671 parentVisible = true; 5672 } 5673 List<ExpandableNotificationRow> children = row.getAttachedChildren(); 5674 if (children != null) { 5675 for (ExpandableNotificationRow childRow : children) { 5676 if (includeChildInDismissAll(row, selection)) { 5677 viewsToRemove.add(childRow); 5678 if (parentVisible && row.areChildrenExpanded()) { 5679 hasClipBounds = childRow.getClipBounds(mTmpRect); 5680 if (childRow.getVisibility() == View.VISIBLE 5681 && (!hasClipBounds || mTmpRect.height() > 0)) { 5682 viewsToHide.add(childRow); 5683 } 5684 } 5685 } 5686 } 5687 } 5688 } 5689 } 5690 5691 // Log dismiss event even if there's nothing to dismiss 5692 mUiEventLogger.log(NotificationPanelEvent.fromSelection(selection)); 5693 5694 if (viewsToRemove.isEmpty()) { 5695 if (closeShade) { 5696 Dependency.get(ShadeController.class).animateCollapsePanels( 5697 CommandQueue.FLAG_EXCLUDE_NONE); 5698 } 5699 return; 5700 } 5701 5702 performDismissAllAnimations( 5703 viewsToHide, 5704 closeShade, 5705 () -> onDismissAllAnimationsEnd(viewsToRemove, selection)); 5706 } 5707 5708 private boolean includeChildInDismissAll( 5709 ExpandableNotificationRow row, 5710 @SelectedRows int selection) { 5711 return canChildBeDismissed(row) && matchesSelection(row, selection); 5712 } 5713 5714 /** 5715 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed. 5716 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete 5717 * handler. 5718 * 5719 * @param hideAnimatedList List of rows to animated away. Should only be views that are 5720 * currently visible, or else the stagger will look funky. 5721 * @param closeShade Whether to close the shade after the stagger animation completes. 5722 * @param onAnimationComplete Called after the entire animation completes (including the shade 5723 * closing if appropriate). The rows must be dismissed for real here. 5724 */ 5725 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5726 private void performDismissAllAnimations( 5727 final ArrayList<View> hideAnimatedList, 5728 final boolean closeShade, 5729 final Runnable onAnimationComplete) { 5730 5731 final Runnable onSlideAwayAnimationComplete = () -> { 5732 if (closeShade) { 5733 Dependency.get(ShadeController.class).addPostCollapseAction(() -> { 5734 setDismissAllInProgress(false); 5735 onAnimationComplete.run(); 5736 }); 5737 Dependency.get(ShadeController.class).animateCollapsePanels( 5738 CommandQueue.FLAG_EXCLUDE_NONE); 5739 } else { 5740 setDismissAllInProgress(false); 5741 onAnimationComplete.run(); 5742 } 5743 }; 5744 5745 if (hideAnimatedList.isEmpty()) { 5746 onSlideAwayAnimationComplete.run(); 5747 return; 5748 } 5749 5750 // let's disable our normal animations 5751 setDismissAllInProgress(true); 5752 5753 // Decrease the delay for every row we animate to give the sense of 5754 // accelerating the swipes 5755 int rowDelayDecrement = 10; 5756 int currentDelay = 140; 5757 int totalDelay = 180; 5758 int numItems = hideAnimatedList.size(); 5759 for (int i = numItems - 1; i >= 0; i--) { 5760 View view = hideAnimatedList.get(i); 5761 Runnable endRunnable = null; 5762 if (i == 0) { 5763 endRunnable = onSlideAwayAnimationComplete; 5764 } 5765 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); 5766 currentDelay = Math.max(50, currentDelay - rowDelayDecrement); 5767 totalDelay += currentDelay; 5768 } 5769 } 5770 5771 @Override 5772 public void setNotificationActivityStarter( 5773 NotificationActivityStarter notificationActivityStarter) { 5774 mNotificationActivityStarter = notificationActivityStarter; 5775 } 5776 5777 @VisibleForTesting 5778 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5779 protected void inflateFooterView() { 5780 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( 5781 R.layout.status_bar_notification_footer, this, false); 5782 footerView.setDismissButtonClickListener(v -> { 5783 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); 5784 clearNotifications(ROWS_ALL, true /* closeShade */); 5785 }); 5786 footerView.setManageButtonClickListener(v -> { 5787 mNotificationActivityStarter.startHistoryIntent(mFooterView.isHistoryShown()); 5788 }); 5789 setFooterView(footerView); 5790 } 5791 5792 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5793 private void inflateEmptyShadeView() { 5794 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate( 5795 R.layout.status_bar_no_notifications, this, false); 5796 view.setText(R.string.empty_shade_text); 5797 view.setOnClickListener(v -> { 5798 final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), 5799 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; 5800 Intent intent = showHistory ? new Intent( 5801 Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( 5802 Settings.ACTION_NOTIFICATION_SETTINGS); 5803 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP); 5804 }); 5805 setEmptyShadeView(view); 5806 } 5807 5808 /** 5809 * Updates expanded, dimmed and locked states of notification rows. 5810 */ 5811 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5812 public void onUpdateRowStates() { 5813 5814 // The following views will be moved to the end of mStackScroller. This counter represents 5815 // the offset from the last child. Initialized to 1 for the very last position. It is post- 5816 // incremented in the following "changeViewPosition" calls so that its value is correct for 5817 // subsequent calls. 5818 int offsetFromEnd = 1; 5819 if (mFgsSectionView != null) { 5820 changeViewPosition(mFgsSectionView, getChildCount() - offsetFromEnd++); 5821 } 5822 changeViewPosition(mFooterView, getChildCount() - offsetFromEnd++); 5823 changeViewPosition(mEmptyShadeView, getChildCount() - offsetFromEnd++); 5824 5825 // No post-increment for this call because it is the last one. Make sure to add one if 5826 // another "changeViewPosition" call is ever added. 5827 changeViewPosition(mShelf, 5828 getChildCount() - offsetFromEnd); 5829 } 5830 5831 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5832 public void setNotificationPanelController( 5833 NotificationPanelViewController notificationPanelViewController) { 5834 mNotificationPanelController = notificationPanelViewController; 5835 } 5836 5837 public void updateIconAreaViews() { 5838 mIconAreaController.updateNotificationIcons(); 5839 } 5840 5841 /** 5842 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the 5843 * notification positions accordingly. 5844 * @param height the new wake up height 5845 * @return the overflow how much the height is further than he lowest notification 5846 */ 5847 public float setPulseHeight(float height) { 5848 mAmbientState.setPulseHeight(height); 5849 if (mKeyguardBypassController.getBypassEnabled()) { 5850 notifyAppearChangedListeners(); 5851 } 5852 requestChildrenUpdate(); 5853 return Math.max(0, height - mAmbientState.getInnerHeight(true /* ignorePulseHeight */)); 5854 } 5855 5856 public float getPulseHeight() { 5857 return mAmbientState.getPulseHeight(); 5858 } 5859 5860 /** 5861 * Set the amount how much we're dozing. This is different from how hidden the shade is, when 5862 * the notification is pulsing. 5863 */ 5864 public void setDozeAmount(float dozeAmount) { 5865 mAmbientState.setDozeAmount(dozeAmount); 5866 updateContinuousBackgroundDrawing(); 5867 requestChildrenUpdate(); 5868 } 5869 5870 public void wakeUpFromPulse() { 5871 setPulseHeight(getWakeUpHeight()); 5872 // Let's place the hidden views at the end of the pulsing notification to make sure we have 5873 // a smooth animation 5874 boolean firstVisibleView = true; 5875 float wakeUplocation = -1f; 5876 int childCount = getChildCount(); 5877 for (int i = 0; i < childCount; i++) { 5878 ExpandableView view = (ExpandableView) getChildAt(i); 5879 if (view.getVisibility() == View.GONE) { 5880 continue; 5881 } 5882 boolean isShelf = view == mShelf; 5883 if (!(view instanceof ExpandableNotificationRow) && !isShelf) { 5884 continue; 5885 } 5886 if (view.getVisibility() == View.VISIBLE && !isShelf) { 5887 if (firstVisibleView) { 5888 firstVisibleView = false; 5889 wakeUplocation = view.getTranslationY() 5890 + view.getActualHeight() - mShelf.getIntrinsicHeight(); 5891 } 5892 } else if (!firstVisibleView) { 5893 view.setTranslationY(wakeUplocation); 5894 } 5895 } 5896 mDimmedNeedsAnimation = true; 5897 } 5898 5899 @Override 5900 public void onDynamicPrivacyChanged() { 5901 if (mIsExpanded) { 5902 // The bottom might change because we're using the final actual height of the view 5903 mAnimateBottomOnLayout = true; 5904 } 5905 // Let's update the footer once the notifications have been updated (in the next frame) 5906 post(() -> { 5907 updateFooter(); 5908 updateSectionBoundaries("dynamic privacy changed"); 5909 }); 5910 } 5911 5912 public void setOnPulseHeightChangedListener(Runnable listener) { 5913 mAmbientState.setOnPulseHeightChangedListener(listener); 5914 } 5915 5916 public float calculateAppearFractionBypass() { 5917 float pulseHeight = getPulseHeight(); 5918 float wakeUpHeight = getWakeUpHeight(); 5919 float dragDownAmount = pulseHeight - wakeUpHeight; 5920 5921 // The total distance required to fully reveal the header 5922 float totalDistance = getIntrinsicPadding(); 5923 return MathUtils.smoothStep(0, totalDistance, dragDownAmount); 5924 } 5925 5926 /** 5927 * A listener that is notified when the empty space below the notifications is clicked on 5928 */ 5929 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5930 public interface OnEmptySpaceClickListener { 5931 void onEmptySpaceClicked(float x, float y); 5932 } 5933 5934 /** 5935 * A listener that gets notified when the overscroll at the top has changed. 5936 */ 5937 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5938 public interface OnOverscrollTopChangedListener { 5939 5940 /** 5941 * Notifies a listener that the overscroll has changed. 5942 * 5943 * @param amount the amount of overscroll, in pixels 5944 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 5945 * unrubberbanded motion to directly expand overscroll view (e.g 5946 * expand 5947 * QS) 5948 */ 5949 void onOverscrollTopChanged(float amount, boolean isRubberbanded); 5950 5951 /** 5952 * Notify a listener that the scroller wants to escape from the scrolling motion and 5953 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 5954 * 5955 * @param velocity The velocity that the Scroller had when over flinging 5956 * @param open Should the fling open or close the overscroll view. 5957 */ 5958 void flingTopOverscroll(float velocity, boolean open); 5959 } 5960 5961 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5962 public void updateSpeedBumpIndex() { 5963 int speedBumpIndex = 0; 5964 int currentIndex = 0; 5965 final int N = getChildCount(); 5966 for (int i = 0; i < N; i++) { 5967 View view = getChildAt(i); 5968 if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) { 5969 continue; 5970 } 5971 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 5972 currentIndex++; 5973 boolean beforeSpeedBump; 5974 if (mHighPriorityBeforeSpeedBump) { 5975 beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT; 5976 } else { 5977 beforeSpeedBump = !row.getEntry().isAmbient(); 5978 } 5979 if (beforeSpeedBump) { 5980 speedBumpIndex = currentIndex; 5981 } 5982 } 5983 boolean noAmbient = speedBumpIndex == N; 5984 updateSpeedBumpIndex(speedBumpIndex, noAmbient); 5985 } 5986 5987 /** Updates the indices of the boundaries between sections. */ 5988 @ShadeViewRefactor(RefactorComponent.INPUT) 5989 public void updateSectionBoundaries(String reason) { 5990 mSectionsManager.updateSectionBoundaries(reason); 5991 } 5992 5993 private void updateContinuousBackgroundDrawing() { 5994 boolean continuousBackground = !mAmbientState.isFullyAwake() 5995 && !mAmbientState.getDraggedViews().isEmpty(); 5996 if (continuousBackground != mContinuousBackgroundUpdate) { 5997 mContinuousBackgroundUpdate = continuousBackground; 5998 if (continuousBackground) { 5999 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater); 6000 } else { 6001 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater); 6002 } 6003 } 6004 } 6005 6006 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 6007 private void updateContinuousShadowDrawing() { 6008 boolean continuousShadowUpdate = mAnimationRunning 6009 || !mAmbientState.getDraggedViews().isEmpty(); 6010 if (continuousShadowUpdate != mContinuousShadowUpdate) { 6011 if (continuousShadowUpdate) { 6012 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater); 6013 } else { 6014 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater); 6015 } 6016 mContinuousShadowUpdate = continuousShadowUpdate; 6017 } 6018 } 6019 6020 @Override 6021 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 6022 public void resetExposedMenuView(boolean animate, boolean force) { 6023 mSwipeHelper.resetExposedMenuView(animate, force); 6024 } 6025 6026 private static boolean matchesSelection( 6027 ExpandableNotificationRow row, 6028 @SelectedRows int selection) { 6029 switch (selection) { 6030 case ROWS_ALL: 6031 return true; 6032 case ROWS_HIGH_PRIORITY: 6033 return row.getEntry().getBucket() < BUCKET_SILENT; 6034 case ROWS_GENTLE: 6035 return row.getEntry().getBucket() == BUCKET_SILENT; 6036 default: 6037 throw new IllegalArgumentException("Unknown selection: " + selection); 6038 } 6039 } 6040 6041 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 6042 static class AnimationEvent { 6043 6044 static AnimationFilter[] FILTERS = new AnimationFilter[]{ 6045 6046 // ANIMATION_TYPE_ADD 6047 new AnimationFilter() 6048 .animateAlpha() 6049 .animateHeight() 6050 .animateTopInset() 6051 .animateY() 6052 .animateZ() 6053 .hasDelays(), 6054 6055 // ANIMATION_TYPE_REMOVE 6056 new AnimationFilter() 6057 .animateAlpha() 6058 .animateHeight() 6059 .animateTopInset() 6060 .animateY() 6061 .animateZ() 6062 .hasDelays(), 6063 6064 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 6065 new AnimationFilter() 6066 .animateHeight() 6067 .animateTopInset() 6068 .animateY() 6069 .animateZ() 6070 .hasDelays(), 6071 6072 // ANIMATION_TYPE_TOP_PADDING_CHANGED 6073 new AnimationFilter() 6074 .animateHeight() 6075 .animateTopInset() 6076 .animateY() 6077 .animateDimmed() 6078 .animateZ(), 6079 6080 // ANIMATION_TYPE_ACTIVATED_CHILD 6081 new AnimationFilter() 6082 .animateZ(), 6083 6084 // ANIMATION_TYPE_DIMMED 6085 new AnimationFilter() 6086 .animateDimmed(), 6087 6088 // ANIMATION_TYPE_CHANGE_POSITION 6089 new AnimationFilter() 6090 .animateAlpha() // maybe the children change positions 6091 .animateHeight() 6092 .animateTopInset() 6093 .animateY() 6094 .animateZ(), 6095 6096 // ANIMATION_TYPE_GO_TO_FULL_SHADE 6097 new AnimationFilter() 6098 .animateHeight() 6099 .animateTopInset() 6100 .animateY() 6101 .animateDimmed() 6102 .animateZ() 6103 .hasDelays(), 6104 6105 // ANIMATION_TYPE_HIDE_SENSITIVE 6106 new AnimationFilter() 6107 .animateHideSensitive(), 6108 6109 // ANIMATION_TYPE_VIEW_RESIZE 6110 new AnimationFilter() 6111 .animateHeight() 6112 .animateTopInset() 6113 .animateY() 6114 .animateZ(), 6115 6116 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 6117 new AnimationFilter() 6118 .animateAlpha() 6119 .animateHeight() 6120 .animateTopInset() 6121 .animateY() 6122 .animateZ(), 6123 6124 // ANIMATION_TYPE_HEADS_UP_APPEAR 6125 new AnimationFilter() 6126 .animateHeight() 6127 .animateTopInset() 6128 .animateY() 6129 .animateZ(), 6130 6131 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 6132 new AnimationFilter() 6133 .animateHeight() 6134 .animateTopInset() 6135 .animateY() 6136 .animateZ() 6137 .hasDelays(), 6138 6139 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 6140 new AnimationFilter() 6141 .animateHeight() 6142 .animateTopInset() 6143 .animateY() 6144 .animateZ() 6145 .hasDelays(), 6146 6147 // ANIMATION_TYPE_HEADS_UP_OTHER 6148 new AnimationFilter() 6149 .animateHeight() 6150 .animateTopInset() 6151 .animateY() 6152 .animateZ(), 6153 6154 // ANIMATION_TYPE_EVERYTHING 6155 new AnimationFilter() 6156 .animateAlpha() 6157 .animateDimmed() 6158 .animateHideSensitive() 6159 .animateHeight() 6160 .animateTopInset() 6161 .animateY() 6162 .animateZ(), 6163 }; 6164 6165 static int[] LENGTHS = new int[]{ 6166 6167 // ANIMATION_TYPE_ADD 6168 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 6169 6170 // ANIMATION_TYPE_REMOVE 6171 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 6172 6173 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 6174 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6175 6176 // ANIMATION_TYPE_TOP_PADDING_CHANGED 6177 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6178 6179 // ANIMATION_TYPE_ACTIVATED_CHILD 6180 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 6181 6182 // ANIMATION_TYPE_DIMMED 6183 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 6184 6185 // ANIMATION_TYPE_CHANGE_POSITION 6186 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6187 6188 // ANIMATION_TYPE_GO_TO_FULL_SHADE 6189 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 6190 6191 // ANIMATION_TYPE_HIDE_SENSITIVE 6192 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6193 6194 // ANIMATION_TYPE_VIEW_RESIZE 6195 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6196 6197 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 6198 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6199 6200 // ANIMATION_TYPE_HEADS_UP_APPEAR 6201 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, 6202 6203 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 6204 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 6205 6206 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 6207 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 6208 6209 // ANIMATION_TYPE_HEADS_UP_OTHER 6210 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6211 6212 // ANIMATION_TYPE_EVERYTHING 6213 StackStateAnimator.ANIMATION_DURATION_STANDARD, 6214 }; 6215 6216 static final int ANIMATION_TYPE_ADD = 0; 6217 static final int ANIMATION_TYPE_REMOVE = 1; 6218 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 6219 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 6220 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4; 6221 static final int ANIMATION_TYPE_DIMMED = 5; 6222 static final int ANIMATION_TYPE_CHANGE_POSITION = 6; 6223 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 7; 6224 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 8; 6225 static final int ANIMATION_TYPE_VIEW_RESIZE = 9; 6226 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 10; 6227 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 11; 6228 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 12; 6229 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13; 6230 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14; 6231 static final int ANIMATION_TYPE_EVERYTHING = 15; 6232 6233 final long eventStartTime; 6234 final ExpandableView mChangingView; 6235 final int animationType; 6236 final AnimationFilter filter; 6237 final long length; 6238 View viewAfterChangingView; 6239 boolean headsUpFromBottom; 6240 6241 AnimationEvent(ExpandableView view, int type) { 6242 this(view, type, LENGTHS[type]); 6243 } 6244 6245 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) { 6246 this(view, type, LENGTHS[type], filter); 6247 } 6248 6249 AnimationEvent(ExpandableView view, int type, long length) { 6250 this(view, type, length, FILTERS[type]); 6251 } 6252 6253 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) { 6254 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 6255 mChangingView = view; 6256 animationType = type; 6257 this.length = length; 6258 this.filter = filter; 6259 } 6260 6261 /** 6262 * Combines the length of several animation events into a single value. 6263 * 6264 * @param events The events of the lengths to combine. 6265 * @return The combined length. Depending on the event types, this might be the maximum of 6266 * all events or the length of a specific event. 6267 */ 6268 static long combineLength(ArrayList<AnimationEvent> events) { 6269 long length = 0; 6270 int size = events.size(); 6271 for (int i = 0; i < size; i++) { 6272 AnimationEvent event = events.get(i); 6273 length = Math.max(length, event.length); 6274 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 6275 return event.length; 6276 } 6277 } 6278 return length; 6279 } 6280 } 6281 6282 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 6283 private final StateListener mStateListener = new StateListener() { 6284 @Override 6285 public void onStatePreChange(int oldState, int newState) { 6286 if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) { 6287 requestAnimateEverything(); 6288 } 6289 } 6290 6291 @Override 6292 public void onStateChanged(int newState) { 6293 setStatusBarState(newState); 6294 } 6295 6296 @Override 6297 public void onStatePostChange() { 6298 NotificationStackScrollLayout.this.onStatePostChange(); 6299 } 6300 }; 6301 6302 @VisibleForTesting 6303 @ShadeViewRefactor(RefactorComponent.INPUT) 6304 protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { 6305 @Override 6306 public void onMenuClicked(View view, int x, int y, MenuItem item) { 6307 if (mLongPressListener == null) { 6308 return; 6309 } 6310 if (view instanceof ExpandableNotificationRow) { 6311 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 6312 mMetricsLogger.write(row.getEntry().getSbn().getLogMaker() 6313 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) 6314 .setType(MetricsEvent.TYPE_ACTION) 6315 ); 6316 } 6317 mLongPressListener.onLongPress(view, x, y, item); 6318 } 6319 6320 @Override 6321 public void onMenuReset(View row) { 6322 View translatingParentView = mSwipeHelper.getTranslatingParentView(); 6323 if (translatingParentView != null && row == translatingParentView) { 6324 mSwipeHelper.clearExposedMenuView(); 6325 mSwipeHelper.clearTranslatingParentView(); 6326 if (row instanceof ExpandableNotificationRow) { 6327 mHeadsUpManager.setMenuShown( 6328 ((ExpandableNotificationRow) row).getEntry(), false); 6329 6330 } 6331 } 6332 } 6333 6334 @Override 6335 public void onMenuShown(View row) { 6336 if (row instanceof ExpandableNotificationRow) { 6337 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; 6338 mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker() 6339 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) 6340 .setType(MetricsEvent.TYPE_ACTION)); 6341 mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true); 6342 mSwipeHelper.onMenuShown(row); 6343 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 6344 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 6345 false /* resetMenu */); 6346 6347 // Check to see if we want to go directly to the notfication guts 6348 NotificationMenuRowPlugin provider = notificationRow.getProvider(); 6349 if (provider.shouldShowGutsOnSnapOpen()) { 6350 MenuItem item = provider.menuItemToExposeOnSnap(); 6351 if (item != null) { 6352 Point origin = provider.getRevealAnimationOrigin(); 6353 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item); 6354 } else { 6355 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no " 6356 + "menu item in menuItemtoExposeOnSnap. Skipping."); 6357 } 6358 6359 // Close the menu row since we went directly to the guts 6360 resetExposedMenuView(false, true); 6361 } 6362 } 6363 } 6364 }; 6365 6366 @ShadeViewRefactor(RefactorComponent.INPUT) 6367 private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = 6368 new NotificationSwipeHelper.NotificationCallback() { 6369 @Override 6370 public void onDismiss() { 6371 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 6372 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 6373 false /* resetMenu */); 6374 } 6375 6376 @Override 6377 public void onSnooze(StatusBarNotification sbn, 6378 NotificationSwipeActionHelper.SnoozeOption snoozeOption) { 6379 mStatusBar.setNotificationSnoozed(sbn, snoozeOption); 6380 } 6381 6382 @Override 6383 public void onSnooze(StatusBarNotification sbn, int hours) { 6384 mStatusBar.setNotificationSnoozed(sbn, hours); 6385 } 6386 6387 @Override 6388 public boolean shouldDismissQuickly() { 6389 return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake(); 6390 } 6391 6392 @Override 6393 public void onDragCancelled(View v) { 6394 setSwipingInProgress(false); 6395 mFalsingManager.onNotificationStopDismissing(); 6396 } 6397 6398 /** 6399 * Handles cleanup after the given {@code view} has been fully swiped out (including 6400 * re-invoking dismiss logic in case the notification has not made its way out yet). 6401 */ 6402 @Override 6403 public void onChildDismissed(View view) { 6404 if (!(view instanceof ActivatableNotificationView)) { 6405 return; 6406 } 6407 ActivatableNotificationView row = (ActivatableNotificationView) view; 6408 if (!row.isDismissed()) { 6409 handleChildViewDismissed(view); 6410 } 6411 ViewGroup transientContainer = row.getTransientContainer(); 6412 if (transientContainer != null) { 6413 transientContainer.removeTransientView(view); 6414 } 6415 } 6416 6417 /** 6418 * Starts up notification dismiss and tells the notification, if any, to remove itself from 6419 * layout. 6420 * 6421 * @param view view (e.g. notification) to dismiss from the layout 6422 */ 6423 6424 public void handleChildViewDismissed(View view) { 6425 setSwipingInProgress(false); 6426 if (mDismissAllInProgress) { 6427 return; 6428 } 6429 6430 boolean isBlockingHelperShown = false; 6431 6432 mAmbientState.onDragFinished(view); 6433 updateContinuousShadowDrawing(); 6434 6435 if (view instanceof ExpandableNotificationRow) { 6436 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 6437 if (row.isHeadsUp()) { 6438 mHeadsUpManager.addSwipedOutNotification( 6439 row.getEntry().getSbn().getKey()); 6440 } 6441 isBlockingHelperShown = 6442 row.performDismissWithBlockingHelper(false /* fromAccessibility */); 6443 } 6444 6445 if (view instanceof PeopleHubView) { 6446 mSectionsManager.hidePeopleRow(); 6447 } 6448 6449 if (!isBlockingHelperShown) { 6450 mSwipedOutViews.add(view); 6451 } 6452 mFalsingManager.onNotificationDismissed(); 6453 if (mFalsingManager.shouldEnforceBouncer()) { 6454 mStatusBar.executeRunnableDismissingKeyguard( 6455 null, 6456 null /* cancelAction */, 6457 false /* dismissShade */, 6458 true /* afterKeyguardGone */, 6459 false /* deferred */); 6460 } 6461 } 6462 6463 @Override 6464 public boolean isAntiFalsingNeeded() { 6465 return onKeyguard(); 6466 } 6467 6468 @Override 6469 public View getChildAtPosition(MotionEvent ev) { 6470 View child = NotificationStackScrollLayout.this.getChildAtPosition( 6471 ev.getX(), 6472 ev.getY(), 6473 true /* requireMinHeight */, 6474 false /* ignoreDecors */); 6475 if (child instanceof ExpandableNotificationRow) { 6476 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 6477 ExpandableNotificationRow parent = row.getNotificationParent(); 6478 if (parent != null && parent.areChildrenExpanded() 6479 && (parent.areGutsExposed() 6480 || mSwipeHelper.getExposedMenuView() == parent 6481 || (parent.getAttachedChildren().size() == 1 6482 && parent.getEntry().isClearable()))) { 6483 // In this case the group is expanded and showing the menu for the 6484 // group, further interaction should apply to the group, not any 6485 // child notifications so we use the parent of the child. We also do the same 6486 // if we only have a single child. 6487 child = parent; 6488 } 6489 } 6490 return child; 6491 } 6492 6493 @Override 6494 public void onBeginDrag(View v) { 6495 mFalsingManager.onNotificationStartDismissing(); 6496 setSwipingInProgress(true); 6497 mAmbientState.onBeginDrag((ExpandableView) v); 6498 updateContinuousShadowDrawing(); 6499 updateContinuousBackgroundDrawing(); 6500 requestChildrenUpdate(); 6501 } 6502 6503 @Override 6504 public void onChildSnappedBack(View animView, float targetLeft) { 6505 mAmbientState.onDragFinished(animView); 6506 updateContinuousShadowDrawing(); 6507 updateContinuousBackgroundDrawing(); 6508 if (animView instanceof ExpandableNotificationRow) { 6509 ExpandableNotificationRow row = (ExpandableNotificationRow) animView; 6510 if (row.isPinned() && !canChildBeDismissed(row) 6511 && row.getEntry().getSbn().getNotification().fullScreenIntent 6512 == null) { 6513 mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(), 6514 true /* removeImmediately */); 6515 } 6516 } 6517 } 6518 6519 @Override 6520 public boolean updateSwipeProgress(View animView, boolean dismissable, 6521 float swipeProgress) { 6522 // Returning true prevents alpha fading. 6523 return !mFadeNotificationsOnDismiss; 6524 } 6525 6526 @Override 6527 public float getFalsingThresholdFactor() { 6528 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 6529 } 6530 6531 @Override 6532 public int getConstrainSwipeStartPosition() { 6533 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); 6534 if (menuRow != null) { 6535 return Math.abs(menuRow.getMenuSnapTarget()); 6536 } 6537 return 0; 6538 } 6539 6540 @Override 6541 public boolean canChildBeDismissed(View v) { 6542 return NotificationStackScrollLayout.canChildBeDismissed(v); 6543 } 6544 6545 @Override 6546 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) { 6547 //TODO: b/131242807 for why this doesn't do anything with direction 6548 return canChildBeDismissed(v); 6549 } 6550 }; 6551 6552 private static boolean canChildBeDismissed(View v) { 6553 if (v instanceof ExpandableNotificationRow) { 6554 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 6555 if (row.isBlockingHelperShowingAndTranslationFinished()) { 6556 return true; 6557 } 6558 if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { 6559 return false; 6560 } 6561 return row.canViewBeDismissed(); 6562 } 6563 if (v instanceof PeopleHubView) { 6564 return ((PeopleHubView) v).getCanSwipe(); 6565 } 6566 return false; 6567 } 6568 6569 // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------ 6570 6571 private void onEntryUpdated(NotificationEntry entry) { 6572 // If the row already exists, the user may have performed a dismiss action on the 6573 // notification. Since it's not clearable we should snap it back. 6574 if (entry.rowExists() && !entry.getSbn().isClearable()) { 6575 snapViewIfNeeded(entry); 6576 } 6577 } 6578 6579 private boolean hasActiveNotifications() { 6580 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { 6581 return !mNotifPipeline.getShadeList().isEmpty(); 6582 } else { 6583 return mEntryManager.hasActiveNotifications(); 6584 } 6585 } 6586 6587 /** 6588 * Called after the animations for a "clear all notifications" action has ended. 6589 */ 6590 private void onDismissAllAnimationsEnd( 6591 List<ExpandableNotificationRow> viewsToRemove, 6592 @SelectedRows int selectedRows) { 6593 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { 6594 if (selectedRows == ROWS_ALL) { 6595 mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId()); 6596 } else { 6597 final List<Pair<NotificationEntry, DismissedByUserStats>> 6598 entriesWithRowsDismissedFromShade = new ArrayList<>(); 6599 final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>(); 6600 final int numVisibleEntries = mNotifPipeline.getShadeListCount(); 6601 for (int i = 0; i < viewsToRemove.size(); i++) { 6602 final NotificationEntry entry = viewsToRemove.get(i).getEntry(); 6603 final DismissedByUserStats stats = 6604 new DismissedByUserStats( 6605 DISMISSAL_SHADE, 6606 DISMISS_SENTIMENT_NEUTRAL, 6607 NotificationVisibility.obtain( 6608 entry.getKey(), 6609 entry.getRanking().getRank(), 6610 numVisibleEntries, 6611 true, 6612 NotificationLogger.getNotificationLocation(entry))); 6613 entriesWithRowsDismissedFromShade.add( 6614 new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)); 6615 } 6616 mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade); 6617 } 6618 } else { 6619 for (ExpandableNotificationRow rowToRemove : viewsToRemove) { 6620 if (canChildBeDismissed(rowToRemove)) { 6621 if (selectedRows == ROWS_ALL) { 6622 // TODO: This is a listener method; we shouldn't be calling it. Can we just 6623 // call performRemoveNotification as below? 6624 mEntryManager.removeNotification( 6625 rowToRemove.getEntry().getKey(), 6626 null /* ranking */, 6627 NotificationListenerService.REASON_CANCEL_ALL); 6628 } else { 6629 mEntryManager.performRemoveNotification( 6630 rowToRemove.getEntry().getSbn(), 6631 NotificationListenerService.REASON_CANCEL_ALL); 6632 } 6633 } else { 6634 rowToRemove.resetTranslation(); 6635 } 6636 } 6637 if (selectedRows == ROWS_ALL) { 6638 try { 6639 mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId()); 6640 } catch (Exception ex) { 6641 } 6642 } 6643 } 6644 } 6645 6646 // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------ 6647 6648 @ShadeViewRefactor(RefactorComponent.INPUT) 6649 private final DragDownCallback mDragDownCallback = new DragDownCallback() { 6650 6651 /* Only ever called as a consequence of a lockscreen expansion gesture. */ 6652 @Override 6653 public boolean onDraggedDown(View startingChild, int dragLengthY) { 6654 boolean canDragDown = hasActiveNotifications() 6655 || mKeyguardMediaController.getView().getVisibility() == VISIBLE; 6656 if (mStatusBarState == StatusBarState.KEYGUARD && canDragDown) { 6657 mLockscreenGestureLogger.write( 6658 MetricsEvent.ACTION_LS_SHADE, 6659 (int) (dragLengthY / mDisplayMetrics.density), 6660 0 /* velocityDp - N/A */); 6661 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN); 6662 6663 if (!mAmbientState.isDozing() || startingChild != null) { 6664 // We have notifications, go to locked shade. 6665 Dependency.get(ShadeController.class).goToLockedShade(startingChild); 6666 if (startingChild instanceof ExpandableNotificationRow) { 6667 ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild; 6668 row.onExpandedByGesture(true /* drag down is always an open */); 6669 } 6670 } 6671 6672 return true; 6673 } else if (mDynamicPrivacyController.isInLockedDownShade()) { 6674 mStatusbarStateController.setLeaveOpenOnKeyguardHide(true); 6675 mStatusBar.dismissKeyguardThenExecute(() -> false /* dismissAction */, 6676 null /* cancelRunnable */, false /* afterKeyguardGone */); 6677 return true; 6678 } else { 6679 // abort gesture. 6680 return false; 6681 } 6682 } 6683 6684 @Override 6685 public void onDragDownReset() { 6686 setDimmed(true /* dimmed */, true /* animated */); 6687 resetScrollPosition(); 6688 resetCheckSnoozeLeavebehind(); 6689 } 6690 6691 @Override 6692 public void onCrossedThreshold(boolean above) { 6693 setDimmed(!above /* dimmed */, true /* animate */); 6694 } 6695 6696 @Override 6697 public void onTouchSlopExceeded() { 6698 cancelLongPress(); 6699 checkSnoozeLeavebehind(); 6700 } 6701 6702 @Override 6703 public void setEmptyDragAmount(float amount) { 6704 mNotificationPanelController.setEmptyDragAmount(amount); 6705 } 6706 6707 @Override 6708 public boolean isFalsingCheckNeeded() { 6709 return mStatusBarState == StatusBarState.KEYGUARD; 6710 } 6711 6712 @Override 6713 public boolean isDragDownEnabledForView(ExpandableView view) { 6714 if (isDragDownAnywhereEnabled()) { 6715 return true; 6716 } 6717 if (mDynamicPrivacyController.isInLockedDownShade()) { 6718 if (view == null) { 6719 // Dragging down is allowed in general 6720 return true; 6721 } 6722 if (view instanceof ExpandableNotificationRow) { 6723 // Only drag down on sensitive views, otherwise the ExpandHelper will take this 6724 return ((ExpandableNotificationRow) view).getEntry().isSensitive(); 6725 } 6726 } 6727 return false; 6728 } 6729 6730 @Override 6731 public boolean isDragDownAnywhereEnabled() { 6732 return mStatusbarStateController.getState() == StatusBarState.KEYGUARD 6733 && !mKeyguardBypassController.getBypassEnabled(); 6734 } 6735 }; 6736 6737 public DragDownCallback getDragDownCallback() { return mDragDownCallback; } 6738 6739 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 6740 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() { 6741 @Override 6742 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 6743 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); 6744 } 6745 6746 @Override 6747 public boolean isExpanded() { 6748 return mIsExpanded; 6749 } 6750 6751 @Override 6752 public Context getContext() { 6753 return mContext; 6754 } 6755 }; 6756 6757 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; } 6758 6759 6760 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 6761 private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() { 6762 @Override 6763 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { 6764 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled 6765 && (mIsExpanded || changedRow.isPinned()); 6766 if (animated) { 6767 mExpandedGroupView = changedRow; 6768 mNeedsAnimation = true; 6769 } 6770 changedRow.setChildrenExpanded(expanded, animated); 6771 if (!mGroupExpandedForMeasure) { 6772 onHeightChanged(changedRow, false /* needsAnimation */); 6773 } 6774 runAfterAnimationFinished(new Runnable() { 6775 @Override 6776 public void run() { 6777 changedRow.onFinishedExpansionChange(); 6778 } 6779 }); 6780 } 6781 6782 @Override 6783 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { 6784 mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren"); 6785 } 6786 6787 @Override 6788 public void onGroupsChanged() { 6789 mStatusBar.requestNotificationUpdate("onGroupsChanged"); 6790 } 6791 }; 6792 6793 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 6794 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() { 6795 @Override 6796 public ExpandableView getChildAtPosition(float touchX, float touchY) { 6797 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY); 6798 } 6799 6800 @Override 6801 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 6802 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); 6803 } 6804 6805 @Override 6806 public boolean canChildBeExpanded(View v) { 6807 return v instanceof ExpandableNotificationRow 6808 && ((ExpandableNotificationRow) v).isExpandable() 6809 && !((ExpandableNotificationRow) v).areGutsExposed() 6810 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); 6811 } 6812 6813 /* Only ever called as a consequence of an expansion gesture in the shade. */ 6814 @Override 6815 public void setUserExpandedChild(View v, boolean userExpanded) { 6816 if (v instanceof ExpandableNotificationRow) { 6817 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 6818 if (userExpanded && onKeyguard()) { 6819 // Due to a race when locking the screen while touching, a notification may be 6820 // expanded even after we went back to keyguard. An example of this happens if 6821 // you click in the empty space while expanding a group. 6822 6823 // We also need to un-user lock it here, since otherwise the content height 6824 // calculated might be wrong. We also can't invert the two calls since 6825 // un-userlocking it will trigger a layout switch in the content view. 6826 row.setUserLocked(false); 6827 updateContentHeight(); 6828 notifyHeightChangeListener(row); 6829 return; 6830 } 6831 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); 6832 row.onExpandedByGesture(userExpanded); 6833 } 6834 } 6835 6836 @Override 6837 public void setExpansionCancelled(View v) { 6838 if (v instanceof ExpandableNotificationRow) { 6839 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); 6840 } 6841 } 6842 6843 @Override 6844 public void setUserLockedChild(View v, boolean userLocked) { 6845 if (v instanceof ExpandableNotificationRow) { 6846 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 6847 } 6848 cancelLongPress(); 6849 requestDisallowInterceptTouchEvent(true); 6850 } 6851 6852 @Override 6853 public void expansionStateChanged(boolean isExpanding) { 6854 mExpandingNotification = isExpanding; 6855 if (!mExpandedInThisMotion) { 6856 if (ANCHOR_SCROLLING) { 6857 // TODO 6858 } else { 6859 mMaxScrollAfterExpand = mOwnScrollY; 6860 } 6861 mExpandedInThisMotion = true; 6862 } 6863 } 6864 6865 @Override 6866 public int getMaxExpandHeight(ExpandableView view) { 6867 return view.getMaxContentHeight(); 6868 } 6869 }; 6870 6871 public ExpandHelper.Callback getExpandHelperCallback() { 6872 return mExpandHelperCallback; 6873 } 6874 6875 /** Enum for selecting some or all notification rows (does not included non-notif views). */ 6876 @Retention(SOURCE) 6877 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE}) 6878 public @interface SelectedRows {} 6879 /** All rows representing notifs. */ 6880 public static final int ROWS_ALL = 0; 6881 /** Only rows where entry.isHighPriority() is true. */ 6882 public static final int ROWS_HIGH_PRIORITY = 1; 6883 /** Only rows where entry.isHighPriority() is false. */ 6884 public static final int ROWS_GENTLE = 2; 6885 6886 /** 6887 * Enum for UiEvent logged from this class 6888 */ 6889 enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { 6890 INVALID(0), 6891 @UiEvent(doc = "User dismissed all notifications from notification panel.") 6892 DISMISS_ALL_NOTIFICATIONS_PANEL(312), 6893 @UiEvent(doc = "User dismissed all silent notifications from notification panel.") 6894 DISMISS_SILENT_NOTIFICATIONS_PANEL(314); 6895 private final int mId; 6896 NotificationPanelEvent(int id) { 6897 mId = id; 6898 } 6899 @Override public int getId() { 6900 return mId; 6901 } 6902 6903 public static UiEventLogger.UiEventEnum fromSelection(@SelectedRows int selection) { 6904 if (selection == ROWS_ALL) { 6905 return DISMISS_ALL_NOTIFICATIONS_PANEL; 6906 } 6907 if (selection == ROWS_GENTLE) { 6908 return DISMISS_SILENT_NOTIFICATIONS_PANEL; 6909 } 6910 if (NotificationStackScrollLayout.DEBUG) { 6911 throw new IllegalArgumentException("Unexpected selection" + selection); 6912 } 6913 return INVALID; 6914 } 6915 } 6916 } 6917