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