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