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