2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.notification.stack;
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;
26 import static java.lang.annotation.RetentionPolicy.SOURCE;
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;
76 import com.android.internal.annotations.VisibleForTesting;
77 import com.android.internal.graphics.ColorUtils;
78 import com.android.internal.logging.MetricsLogger;
79 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
80 import com.android.internal.statusbar.IStatusBarService;
81 import com.android.keyguard.KeyguardSliceView;
82 import com.android.settingslib.Utils;
83 import com.android.systemui.Dependency;
84 import com.android.systemui.Dumpable;
85 import com.android.systemui.ExpandHelper;
86 import com.android.systemui.Interpolators;
87 import com.android.systemui.R;
88 import com.android.systemui.SwipeHelper;
89 import com.android.systemui.classifier.FalsingManagerFactory;
90 import com.android.systemui.colorextraction.SysuiColorExtractor;
91 import com.android.systemui.plugins.ActivityStarter;
92 import com.android.systemui.plugins.FalsingManager;
93 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
94 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
95 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
96 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
97 import com.android.systemui.plugins.statusbar.StatusBarStateController;
98 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
99 import com.android.systemui.statusbar.CommandQueue;
100 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
101 import com.android.systemui.statusbar.EmptyShadeView;
102 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
103 import com.android.systemui.statusbar.NotificationRemoteInputManager;
104 import com.android.systemui.statusbar.NotificationShelf;
105 import com.android.systemui.statusbar.RemoteInputController;
106 import com.android.systemui.statusbar.StatusBarState;
107 import com.android.systemui.statusbar.SysuiStatusBarStateController;
108 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
109 import com.android.systemui.statusbar.notification.FakeShadowView;
110 import com.android.systemui.statusbar.notification.NotificationEntryListener;
111 import com.android.systemui.statusbar.notification.NotificationEntryManager;
112 import com.android.systemui.statusbar.notification.NotificationUtils;
113 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
114 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
115 import com.android.systemui.statusbar.notification.VisualStabilityManager;
116 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
117 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
118 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
119 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
120 import com.android.systemui.statusbar.notification.row.ExpandableView;
121 import com.android.systemui.statusbar.notification.row.FooterView;
122 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
123 import com.android.systemui.statusbar.notification.row.NotificationGuts;
124 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
125 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
126 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
127 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
128 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
129 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
130 import com.android.systemui.statusbar.phone.KeyguardBypassController;
131 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
132 import com.android.systemui.statusbar.phone.NotificationGroupManager;
133 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
134 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
135 import com.android.systemui.statusbar.phone.NotificationPanelView;
136 import com.android.systemui.statusbar.phone.ScrimController;
137 import com.android.systemui.statusbar.phone.ShadeController;
138 import com.android.systemui.statusbar.phone.StatusBar;
139 import com.android.systemui.statusbar.policy.ConfigurationController;
140 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
141 import com.android.systemui.statusbar.policy.HeadsUpUtil;
142 import com.android.systemui.statusbar.policy.ScrollAdapter;
143 import com.android.systemui.tuner.TunerService;
144 import com.android.systemui.util.Assert;
146 import java.io.FileDescriptor;
147 import java.io.PrintWriter;
148 import java.lang.annotation.Retention;
149 import java.util.ArrayList;
150 import java.util.Collections;
151 import java.util.Comparator;
152 import java.util.HashSet;
153 import java.util.List;
154 import java.util.function.BiConsumer;
156 import javax.inject.Inject;
157 import javax.inject.Named;
160 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
162 public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
163 NotificationListContainer, ConfigurationListener, Dumpable,
164 DynamicPrivacyController.Listener {
166 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
167 private static final String TAG = "StackScroller";
168 private static final boolean DEBUG = false;
169 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
170 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
171 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
173 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
175 private static final int INVALID_POINTER = -1;
176 static final int NUM_SECTIONS = 2;
178 * The distance in pixels between sections when the sections are directly adjacent (no visible
179 * gap is drawn between them). In this case we don't want to round their corners.
181 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
182 private final KeyguardBypassController mKeyguardBypassController;
183 private final DynamicPrivacyController mDynamicPrivacyController;
184 private final SysuiStatusBarStateController mStatusbarStateController;
186 private ExpandHelper mExpandHelper;
187 private final NotificationSwipeHelper mSwipeHelper;
188 private int mCurrentStackHeight = Integer.MAX_VALUE;
189 private final Paint mBackgroundPaint = new Paint();
190 private final boolean mShouldDrawNotificationBackground;
191 private boolean mHighPriorityBeforeSpeedBump;
192 private final boolean mAllowLongPress;
193 private boolean mDismissRtl;
195 private float mExpandedHeight;
196 private int mOwnScrollY;
197 private View mScrollAnchorView;
198 private int mScrollAnchorViewY;
199 private int mMaxLayoutHeight;
201 private VelocityTracker mVelocityTracker;
202 private OverScroller mScroller;
203 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
204 private int mLastScrollerY;
206 * True if the max position was set to a known position on the last call to {@link #mScroller}.
208 private boolean mIsScrollerBoundSet;
209 private Runnable mFinishScrollingCallback;
210 private int mTouchSlop;
211 private int mMinimumVelocity;
212 private int mMaximumVelocity;
213 private int mOverflingDistance;
214 private float mMaxOverScroll;
215 private boolean mIsBeingDragged;
216 private int mLastMotionY;
218 private int mActivePointerId = INVALID_POINTER;
219 private boolean mTouchIsClick;
220 private float mInitialTouchX;
221 private float mInitialTouchY;
223 private Paint mDebugPaint;
224 private int mContentHeight;
225 private int mIntrinsicContentHeight;
226 private int mCollapsedSize;
227 private int mPaddingBetweenElements;
228 private int mIncreasedPaddingBetweenElements;
229 private int mMaxTopPadding;
230 private int mTopPadding;
231 private int mBottomMargin;
232 private int mBottomInset = 0;
233 private float mQsExpansionFraction;
236 * The algorithm which calculates the properties for our children
238 protected final StackScrollAlgorithm mStackScrollAlgorithm;
240 private final AmbientState mAmbientState;
241 private NotificationGroupManager mGroupManager;
242 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
243 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
244 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
245 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
246 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
247 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
248 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
249 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
250 private boolean mAnimationsEnabled;
251 private boolean mChangePositionInProgress;
252 private boolean mChildTransferInProgress;
255 * The raw amount of the overScroll on the top, which is not rubber-banded.
257 private float mOverScrolledTopPixels;
260 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
262 private float mOverScrolledBottomPixels;
263 private NotificationLogger.OnChildLocationsChangedListener mListener;
264 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
265 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
266 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
267 private boolean mNeedsAnimation;
268 private boolean mTopPaddingNeedsAnimation;
269 private boolean mDimmedNeedsAnimation;
270 private boolean mHideSensitiveNeedsAnimation;
271 private boolean mActivateNeedsAnimation;
272 private boolean mGoToFullShadeNeedsAnimation;
273 private boolean mIsExpanded = true;
274 private boolean mChildrenUpdateRequested;
275 private boolean mIsExpansionChanging;
276 private boolean mPanelTracking;
277 private boolean mExpandingNotification;
278 private boolean mExpandedInThisMotion;
279 private boolean mShouldShowShelfOnly;
280 protected boolean mScrollingEnabled;
281 protected FooterView mFooterView;
282 protected EmptyShadeView mEmptyShadeView;
283 private boolean mDismissAllInProgress;
284 private boolean mFadeNotificationsOnDismiss;
287 * Was the scroller scrolled to the top when the down motion was observed?
289 private boolean mScrolledToTopOnFirstDown;
291 * The minimal amount of over scroll which is needed in order to switch to the quick settings
292 * when over scrolling on a expanded card.
294 private float mMinTopOverScrollToEscape;
295 private int mIntrinsicPadding;
296 private float mStackTranslation;
297 private float mTopPaddingOverflow;
298 private boolean mDontReportNextOverScroll;
299 private boolean mDontClampNextScroll;
300 private boolean mNeedViewResizeAnimation;
301 private ExpandableView mExpandedGroupView;
302 private boolean mEverythingNeedsAnimation;
305 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
306 * This is needed to avoid scrolling too far after the notification was collapsed in the same
309 private int mMaxScrollAfterExpand;
310 private ExpandableNotificationRow.LongPressListener mLongPressListener;
311 boolean mCheckForLeavebehind;
314 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
317 private boolean mOnlyScrollingInThisMotion;
318 private boolean mDisallowDismissInThisMotion;
319 private boolean mDisallowScrollingInThisMotion;
320 private long mGoToFullShadeDelay;
321 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
322 = new ViewTreeObserver.OnPreDrawListener() {
324 public boolean onPreDraw() {
325 updateForcedScroll();
327 mChildrenUpdateRequested = false;
328 getViewTreeObserver().removeOnPreDrawListener(this);
332 private StatusBar mStatusBar;
333 private int[] mTempInt2 = new int[2];
334 private boolean mGenerateChildOrderChangedEvent;
335 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
336 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
337 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
339 private HeadsUpManagerPhone mHeadsUpManager;
340 private final NotificationRoundnessManager mRoundnessManager;
341 private boolean mTrackingHeadsUp;
342 private ScrimController mScrimController;
343 private boolean mForceNoOverlappingRendering;
344 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
345 private FalsingManager mFalsingManager;
346 private boolean mAnimationRunning;
347 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
348 = new ViewTreeObserver.OnPreDrawListener() {
350 public boolean onPreDraw() {
351 onPreDrawDuringAnimation();
355 private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
356 private boolean mAnimateNextBackgroundTop;
357 private boolean mAnimateNextBackgroundBottom;
358 private boolean mAnimateNextSectionBoundsChange;
359 private int mBgColor;
360 private float mDimAmount;
361 private ValueAnimator mDimAnimator;
362 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
363 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
365 public void onAnimationEnd(Animator animation) {
369 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
370 = new ValueAnimator.AnimatorUpdateListener() {
373 public void onAnimationUpdate(ValueAnimator animation) {
374 setDimAmount((Float) animation.getAnimatedValue());
377 protected ViewGroup mQsContainer;
378 private boolean mContinuousShadowUpdate;
379 private boolean mContinuousBackgroundUpdate;
380 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
381 = new ViewTreeObserver.OnPreDrawListener() {
384 public boolean onPreDraw() {
389 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
393 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
395 public int compare(ExpandableView view, ExpandableView otherView) {
396 float endY = view.getTranslationY() + view.getActualHeight();
397 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
398 if (endY < otherEndY) {
400 } else if (endY > otherEndY) {
403 // The two notifications end at the same location
408 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
410 public void getOutline(View view, Outline outline) {
411 if (mAmbientState.isHiddenAtAll()) {
412 float xProgress = mHideXInterpolator.getInterpolation(
413 (1 - mLinearHideAmount) * mBackgroundXFactor);
414 outline.setRoundRect(mBackgroundAnimationRect,
415 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius,
417 outline.setAlpha(1.0f - mAmbientState.getHideAmount());
419 ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
423 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
424 private boolean mPulsing;
425 private boolean mGroupExpandedForMeasure;
426 private boolean mScrollable;
427 private View mForcedScroll;
430 * @see #setHideAmount(float, float)
432 private float mInterpolatedHideAmount = 0f;
435 * @see #setHideAmount(float, float)
437 private float mLinearHideAmount = 0f;
440 * How fast the background scales in the X direction as a factor of the Y expansion.
442 private float mBackgroundXFactor = 1f;
444 private boolean mSwipingInProgress;
446 private boolean mUsingLightTheme;
447 private boolean mQsExpanded;
448 private boolean mForwardScrollable;
449 private boolean mBackwardScrollable;
450 private NotificationShelf mShelf;
451 private int mMaxDisplayedNotifications = -1;
452 private int mStatusBarHeight;
453 private int mMinInteractionHeight;
454 private boolean mNoAmbient;
455 private final Rect mClipRect = new Rect();
456 private boolean mIsClipped;
457 private Rect mRequestedClipBounds;
458 private boolean mInHeadsUpPinnedMode;
459 private boolean mHeadsUpAnimatingAway;
460 private int mStatusBarState;
461 private int mCachedBackgroundColor;
462 private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
463 private Runnable mReflingAndAnimateScroll = () -> {
464 if (ANCHOR_SCROLLING) {
465 maybeReflingScroller();
469 private int mCornerRadius;
470 private int mSidePaddings;
471 private final Rect mBackgroundAnimationRect = new Rect();
472 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
473 private int mHeadsUpInset;
474 private HeadsUpAppearanceController mHeadsUpAppearanceController;
475 private NotificationIconAreaController mIconAreaController;
476 private final NotificationLockscreenUserManager mLockscreenUserManager =
477 Dependency.get(NotificationLockscreenUserManager.class);
478 private final Rect mTmpRect = new Rect();
479 private final NotificationEntryManager mEntryManager =
480 Dependency.get(NotificationEntryManager.class);
481 private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
482 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
484 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
485 private final NotificationRemoteInputManager mRemoteInputManager =
486 Dependency.get(NotificationRemoteInputManager.class);
487 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
489 private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
490 private final LockscreenGestureLogger mLockscreenGestureLogger =
491 Dependency.get(LockscreenGestureLogger.class);
492 private final VisualStabilityManager mVisualStabilityManager =
493 Dependency.get(VisualStabilityManager.class);
494 protected boolean mClearAllEnabled;
496 private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
497 private NotificationPanelView mNotificationPanel;
498 private final ShadeController mShadeController = Dependency.get(ShadeController.class);
500 private final NotificationGutsManager
501 mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
502 private final NotificationSectionsManager mSectionsManager;
503 private boolean mAnimateBottomOnLayout;
504 private float mLastSentAppear;
505 private float mLastSentExpandedHeight;
506 private boolean mWillExpand;
509 public NotificationStackScrollLayout(
510 @Named(VIEW_CONTEXT) Context context,
512 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
513 NotificationRoundnessManager notificationRoundnessManager,
514 DynamicPrivacyController dynamicPrivacyController,
515 ConfigurationController configurationController,
516 ActivityStarter activityStarter,
517 StatusBarStateController statusBarStateController,
518 HeadsUpManagerPhone headsUpManager,
519 KeyguardBypassController keyguardBypassController) {
520 super(context, attrs, 0, 0);
521 Resources res = getResources();
523 mAllowLongPress = allowLongPress;
525 for (int i = 0; i < NUM_SECTIONS; i++) {
526 mSections[i] = new NotificationSection(this);
528 mRoundnessManager = notificationRoundnessManager;
530 mHeadsUpManager = headsUpManager;
531 mHeadsUpManager.addListener(mRoundnessManager);
532 mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
533 mKeyguardBypassController = keyguardBypassController;
536 new NotificationSectionsManager(
539 statusBarStateController,
540 configurationController,
541 NotificationUtils.useNewInterruptionModel(context));
542 mSectionsManager.initialize(LayoutInflater.from(context));
543 mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
544 // Leave the shade open if there will be other notifs left over to clear
545 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
546 clearNotifications(ROWS_GENTLE, closeShade);
549 mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
550 mBgColor = context.getColor(R.color.notification_shade_background_color);
551 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
552 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
553 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
554 minHeight, maxHeight);
555 mExpandHelper.setEventSource(this);
556 mExpandHelper.setScrollAdapter(this);
557 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
558 getContext(), mMenuEventListener);
559 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
561 mFalsingManager = FalsingManagerFactory.getInstance(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 setOutlineProvider(mOutlineProvider);
571 // Blocking helper manager wants to know the expanded state, update as well.
572 NotificationBlockingHelperManager blockingHelperManager =
573 Dependency.get(NotificationBlockingHelperManager.class);
574 addOnExpandedHeightChangedListener((height, unused) -> {
575 blockingHelperManager.setNotificationShadeExpanded(height);
578 boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
579 setWillNotDraw(!willDraw);
580 mBackgroundPaint.setAntiAlias(true);
582 mDebugPaint = new Paint();
583 mDebugPaint.setColor(0xffff0000);
584 mDebugPaint.setStrokeWidth(2);
585 mDebugPaint.setStyle(Paint.Style.STROKE);
586 mDebugPaint.setTextSize(25f);
588 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
590 TunerService tunerService = Dependency.get(TunerService.class);
591 tunerService.addTunable((key, newValue) -> {
592 if (key.equals(HIGH_PRIORITY)) {
593 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
594 } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
595 updateDismissRtlSetting("1".equals(newValue));
597 }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
599 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
601 public void onPostEntryUpdated(NotificationEntry entry) {
602 if (!entry.notification.isClearable()) {
603 // The user may have performed a dismiss action on the notification, since it's
604 // not clearable we should snap it back.
605 snapViewIfNeeded(entry);
609 dynamicPrivacyController.addListener(this);
610 mDynamicPrivacyController = dynamicPrivacyController;
611 mStatusbarStateController = (SysuiStatusBarStateController) statusBarStateController;
614 private void updateDismissRtlSetting(boolean dismissRtl) {
615 mDismissRtl = dismissRtl;
616 for (int i = 0; i < getChildCount(); i++) {
617 View child = getChildAt(i);
618 if (child instanceof ExpandableNotificationRow) {
619 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
625 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
626 protected void onFinishInflate() {
627 super.onFinishInflate();
629 inflateEmptyShadeView();
631 mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
632 if (mAllowLongPress) {
633 setLongPressListener(mNotificationGutsManager::openGuts);
638 * @return the height at which we will wake up when pulsing
640 public float getWakeUpHeight() {
641 ActivatableNotificationView firstChild = getFirstChildWithBackground();
642 if (firstChild != null) {
643 if (mKeyguardBypassController.getBypassEnabled()) {
644 return firstChild.getHeadsUpHeightWithoutHeader();
646 return firstChild.getCollapsedHeight();
653 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
654 public void onDensityOrFontScaleChanged() {
658 private void reinflateViews() {
660 inflateEmptyShadeView();
662 mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
666 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
667 public void onThemeChanged() {
668 final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
669 updateDecorViews(useDarkText);
675 public void onOverlayChanged() {
676 int newRadius = mContext.getResources().getDimensionPixelSize(
677 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
678 if (mCornerRadius != newRadius) {
679 mCornerRadius = newRadius;
686 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
687 public void updateFooter() {
688 boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
689 boolean showFooterView = (showDismissView ||
690 mEntryManager.getNotificationData().getActiveNotifications().size() != 0)
691 && mStatusBarState != StatusBarState.KEYGUARD
692 && !mRemoteInputManager.getController().isRemoteInputActive();
694 updateFooterView(showFooterView, showDismissView);
698 * Return whether there are any clearable notifications
700 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
701 public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
702 if (mDynamicPrivacyController.isInLockedDownShade()) {
705 int childCount = getChildCount();
706 for (int i = 0; i < childCount; i++) {
707 View child = getChildAt(i);
708 if (!(child instanceof ExpandableNotificationRow)) {
711 final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
712 if (row.canViewBeDismissed() && matchesSelection(row, selection)) {
719 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
720 public RemoteInputController.Delegate createDelegate() {
721 return new RemoteInputController.Delegate() {
722 public void setRemoteInputActive(NotificationEntry entry,
723 boolean remoteInputActive) {
724 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
725 entry.notifyHeightChanged(true /* needsAnimation */);
729 public void lockScrollTo(NotificationEntry entry) {
730 NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
733 public void requestDisallowLongPressAndDismiss() {
734 requestDisallowLongPress();
735 requestDisallowDismiss();
741 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
742 protected void onAttachedToWindow() {
743 super.onAttachedToWindow();
744 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
745 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
746 Dependency.get(ConfigurationController.class).addCallback(this);
750 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
751 protected void onDetachedFromWindow() {
752 super.onDetachedFromWindow();
753 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
754 Dependency.get(ConfigurationController.class).removeCallback(this);
758 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
759 public NotificationSwipeActionHelper getSwipeActionHelper() {
764 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
765 public void onUiModeChanged() {
766 mBgColor = mContext.getColor(R.color.notification_shade_background_color);
767 updateBackgroundDimming();
768 mShelf.onUiModeChanged();
769 mSectionsManager.onUiModeChanged();
772 @ShadeViewRefactor(RefactorComponent.DECORATOR)
773 protected void onDraw(Canvas canvas) {
774 if (mShouldDrawNotificationBackground
775 && (mSections[0].getCurrentBounds().top
776 < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
777 || mAmbientState.isDozing())) {
778 drawBackground(canvas);
779 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
780 drawHeadsUpBackground(canvas);
785 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
786 y = getLayoutHeight();
787 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
788 y = getHeight() - getEmptyBottomMargin();
789 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
794 public void draw(Canvas canvas) {
797 if (DEBUG && ANCHOR_SCROLLING) {
798 if (mScrollAnchorView instanceof ExpandableNotificationRow) {
800 mScrollAnchorView.getTranslationY(),
802 mScrollAnchorView.getTranslationY()
803 + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
805 canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
806 mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
807 int y = (int) mShelf.getTranslationY();
808 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
810 canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
811 getTopPadding() + 30, mDebugPaint);
812 canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
813 getHeight() - 30, mDebugPaint);
817 @ShadeViewRefactor(RefactorComponent.DECORATOR)
818 private void drawBackground(Canvas canvas) {
819 int lockScreenLeft = mSidePaddings;
820 int lockScreenRight = getWidth() - mSidePaddings;
821 int lockScreenTop = mSections[0].getCurrentBounds().top;
822 int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
823 int hiddenLeft = getWidth() / 2;
824 int hiddenTop = mTopPadding;
826 float yProgress = 1 - mInterpolatedHideAmount;
827 float xProgress = mHideXInterpolator.getInterpolation(
828 (1 - mLinearHideAmount) * mBackgroundXFactor);
830 int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
831 int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
832 int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
833 int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
834 mBackgroundAnimationRect.set(
840 int backgroundTopAnimationOffset = top - lockScreenTop;
841 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
842 boolean anySectionHasVisibleChild = false;
843 for (NotificationSection section : mSections) {
844 if (section.getFirstVisibleChild() != null) {
845 anySectionHasVisibleChild = true;
849 boolean shouldDrawBackground;
850 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
851 shouldDrawBackground = isPulseExpanding();
853 shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
855 if (shouldDrawBackground) {
856 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
863 * Draws round rects for each background section.
865 * We want to draw a round rect for each background section as defined by {@link #mSections}.
866 * However, if two sections are directly adjacent with no gap between them (e.g. on the
867 * lockscreen where the shelf can appear directly below the high priority section, or while
868 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
869 * section), we don't want to round the adjacent corners.
871 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
872 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
873 * This method tracks the top of each rect we need to draw, then iterates through the visible
874 * sections. If a section is not adjacent to the previous section, we draw the previous rect
875 * behind the sections we've accumulated up to that point, then start a new rect at the top of
876 * the current section. When we're done iterating we will always have one rect left to draw.
878 private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
879 int animationYOffset) {
880 int backgroundRectTop = top;
881 int lastSectionBottom =
882 mSections[0].getCurrentBounds().bottom + animationYOffset;
883 int currentLeft = left;
884 int currentRight = right;
885 boolean first = true;
886 for (NotificationSection section : mSections) {
887 if (section.getFirstVisibleChild() == null) {
890 int sectionTop = section.getCurrentBounds().top + animationYOffset;
891 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
892 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
893 // If sections are directly adjacent to each other, we don't want to draw them
894 // as separate roundrects, as the rounded corners right next to each other look
896 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
897 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
898 canvas.drawRoundRect(currentLeft,
902 mCornerRadius, mCornerRadius, mBackgroundPaint);
903 backgroundRectTop = sectionTop;
905 currentLeft = ownLeft;
906 currentRight = ownRight;
908 section.getCurrentBounds().bottom + animationYOffset;
911 canvas.drawRoundRect(currentLeft,
915 mCornerRadius, mCornerRadius, mBackgroundPaint);
918 private void drawHeadsUpBackground(Canvas canvas) {
919 int left = mSidePaddings;
920 int right = getWidth() - mSidePaddings;
922 float top = getHeight();
924 int childCount = getChildCount();
925 for (int i = 0; i < childCount; i++) {
926 View child = getChildAt(i);
927 if (child.getVisibility() != View.GONE
928 && child instanceof ExpandableNotificationRow) {
929 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
930 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
931 && row.getProvider().shouldShowGutsOnSnapOpen()) {
932 top = Math.min(top, row.getTranslationY());
933 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
939 canvas.drawRoundRect(
940 left, top, right, bottom,
941 mCornerRadius, mCornerRadius, mBackgroundPaint);
945 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
946 private void updateBackgroundDimming() {
947 // No need to update the background color if it's not being drawn.
948 if (!mShouldDrawNotificationBackground) {
952 // Interpolate between semi-transparent notification panel background color
953 // and white AOD separator.
954 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
956 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
958 if (mCachedBackgroundColor != color) {
959 mCachedBackgroundColor = color;
960 mBackgroundPaint.setColor(color);
965 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
966 private void initView(Context context) {
967 mScroller = new OverScroller(getContext());
968 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
969 setClipChildren(false);
970 final ViewConfiguration configuration = ViewConfiguration.get(context);
971 mTouchSlop = configuration.getScaledTouchSlop();
972 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
973 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
974 mOverflingDistance = configuration.getScaledOverflingDistance();
976 Resources res = context.getResources();
977 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
978 mStackScrollAlgorithm.initView(context);
979 mAmbientState.reload(context);
980 mPaddingBetweenElements = Math.max(1,
981 res.getDimensionPixelSize(R.dimen.notification_divider_height));
982 mIncreasedPaddingBetweenElements =
983 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
984 mMinTopOverScrollToEscape = res.getDimensionPixelSize(
985 R.dimen.min_top_overscroll_to_qs);
986 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
987 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
988 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
989 mMinInteractionHeight = res.getDimensionPixelSize(
990 R.dimen.notification_min_interaction_height);
991 mCornerRadius = res.getDimensionPixelSize(
992 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
993 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
994 R.dimen.heads_up_status_bar_padding);
997 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
998 private void notifyHeightChangeListener(ExpandableView view) {
999 notifyHeightChangeListener(view, false /* needsAnimation */);
1002 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1003 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
1004 if (mOnHeightChangedListener != null) {
1005 mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
1009 public boolean isPulseExpanding() {
1010 return mAmbientState.isPulseExpanding();
1014 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1015 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1016 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1018 int width = MeasureSpec.getSize(widthMeasureSpec);
1019 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
1020 MeasureSpec.getMode(widthMeasureSpec));
1021 // Don't constrain the height of the children so we know how big they'd like to be
1022 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
1023 MeasureSpec.UNSPECIFIED);
1025 // We need to measure all children even the GONE ones, such that the heights are calculated
1026 // correctly as they are used to calculate how many we can fit on the screen.
1027 final int size = getChildCount();
1028 for (int i = 0; i < size; i++) {
1029 measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
1034 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1035 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1036 // we layout all our children centered on the top
1037 float centerX = getWidth() / 2.0f;
1038 for (int i = 0; i < getChildCount(); i++) {
1039 View child = getChildAt(i);
1040 // We need to layout all children even the GONE ones, such that the heights are
1041 // calculated correctly as they are used to calculate how many we can fit on the screen
1042 float width = child.getMeasuredWidth();
1043 float height = child.getMeasuredHeight();
1044 child.layout((int) (centerX - width / 2.0f),
1046 (int) (centerX + width / 2.0f),
1049 setMaxLayoutHeight(getHeight());
1050 updateContentHeight();
1051 clampScrollPosition();
1052 requestChildrenUpdate();
1053 updateFirstAndLastBackgroundViews();
1054 updateAlgorithmLayoutMinHeight();
1055 updateOwnTranslationZ();
1058 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1059 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
1060 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
1061 mNeedViewResizeAnimation = true;
1062 mNeedsAnimation = true;
1066 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1067 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
1068 mAmbientState.setSpeedBumpIndex(newIndex);
1069 mNoAmbient = noAmbient;
1073 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1074 public void setChildLocationsChangedListener(
1075 NotificationLogger.OnChildLocationsChangedListener listener) {
1076 mListener = listener;
1080 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
1081 public boolean isInVisibleLocation(NotificationEntry entry) {
1082 ExpandableNotificationRow row = entry.getRow();
1083 ExpandableViewState childViewState = row.getViewState();
1085 if (childViewState == null) {
1088 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
1091 if (row.getVisibility() != View.VISIBLE) {
1097 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
1098 private void setMaxLayoutHeight(int maxLayoutHeight) {
1099 mMaxLayoutHeight = maxLayoutHeight;
1100 mShelf.setMaxLayoutHeight(maxLayoutHeight);
1101 updateAlgorithmHeightAndPadding();
1104 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
1105 private void updateAlgorithmHeightAndPadding() {
1106 mAmbientState.setLayoutHeight(getLayoutHeight());
1107 updateAlgorithmLayoutMinHeight();
1108 mAmbientState.setTopPadding(mTopPadding);
1111 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
1112 private void updateAlgorithmLayoutMinHeight() {
1113 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition()
1114 ? getLayoutMinHeight() : 0);
1118 * Updates the children views according to the stack scroll algorithm. Call this whenever
1119 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
1121 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1122 private void updateChildren() {
1123 updateScrollStateForAddedChildren();
1124 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
1126 : mScroller.getCurrVelocity());
1127 if (ANCHOR_SCROLLING) {
1128 mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
1129 mAmbientState.setAnchorViewY(mScrollAnchorViewY);
1131 mAmbientState.setScrollY(mOwnScrollY);
1133 mStackScrollAlgorithm.resetViewStates(mAmbientState);
1134 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
1135 applyCurrentState();
1137 startAnimationToState();
1141 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1142 private void onPreDrawDuringAnimation() {
1143 mShelf.updateAppearance();
1144 updateClippingToTopRoundedCorner();
1145 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
1150 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1151 private void updateClippingToTopRoundedCorner() {
1152 Float clipStart = (float) mTopPadding
1154 + mAmbientState.getExpandAnimationTopChange();
1155 Float clipEnd = clipStart + mCornerRadius;
1156 boolean first = true;
1157 for (int i = 0; i < getChildCount(); i++) {
1158 ExpandableView child = (ExpandableView) getChildAt(i);
1159 if (child.getVisibility() == GONE) {
1162 float start = child.getTranslationY();
1163 float end = start + child.getActualHeight();
1164 boolean clip = clipStart > start && clipStart < end
1165 || clipEnd >= start && clipEnd <= end;
1166 clip &= !(first && isScrolledToTop());
1167 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
1168 : ExpandableView.NO_ROUNDNESS);
1173 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1174 private void updateScrollStateForAddedChildren() {
1175 if (mChildrenToAddAnimated.isEmpty()) {
1178 if (!ANCHOR_SCROLLING) {
1179 for (int i = 0; i < getChildCount(); i++) {
1180 ExpandableView child = (ExpandableView) getChildAt(i);
1181 if (mChildrenToAddAnimated.contains(child)) {
1182 int startingPosition = getPositionInLinearLayout(child);
1183 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
1184 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
1185 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
1186 int childHeight = getIntrinsicHeight(child) + padding;
1187 if (startingPosition < mOwnScrollY) {
1188 // This child starts off screen, so let's keep it offscreen to keep the
1191 setOwnScrollY(mOwnScrollY + childHeight);
1196 clampScrollPosition();
1199 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1200 private void updateForcedScroll() {
1201 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
1202 || !mForcedScroll.isAttachedToWindow())) {
1203 mForcedScroll = null;
1205 if (mForcedScroll != null) {
1206 ExpandableView expandableView = (ExpandableView) mForcedScroll;
1207 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
1208 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1209 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
1211 if (ANCHOR_SCROLLING) {
1214 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
1216 // Only apply the scroll if we're scrolling the view upwards, or the view is so
1217 // far up that it is not visible anymore.
1218 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1219 setOwnScrollY(targetScroll);
1225 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1226 private void requestChildrenUpdate() {
1227 if (!mChildrenUpdateRequested) {
1228 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
1229 mChildrenUpdateRequested = true;
1235 * Returns best effort count of visible notifications.
1237 public int getVisibleNotificationCount() {
1239 for (int i = 0; i < getChildCount(); i++) {
1240 final View child = getChildAt(i);
1241 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
1248 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1249 private boolean isCurrentlyAnimating() {
1250 return mStateAnimator.isRunning();
1253 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1254 private void clampScrollPosition() {
1255 if (ANCHOR_SCROLLING) {
1258 int scrollRange = getScrollRange();
1259 if (scrollRange < mOwnScrollY) {
1260 setOwnScrollY(scrollRange);
1265 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1266 public int getTopPadding() {
1270 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1271 private void setTopPadding(int topPadding, boolean animate) {
1272 if (mTopPadding != topPadding) {
1273 mTopPadding = topPadding;
1274 updateAlgorithmHeightAndPadding();
1275 updateContentHeight();
1276 if (animate && mAnimationsEnabled && mIsExpanded) {
1277 mTopPaddingNeedsAnimation = true;
1278 mNeedsAnimation = true;
1280 requestChildrenUpdate();
1281 notifyHeightChangeListener(null, animate);
1286 * Update the height of the panel.
1288 * @param height the expanded height of the panel
1290 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1291 public void setExpandedHeight(float height) {
1292 mExpandedHeight = height;
1293 setIsExpanded(height > 0);
1294 int minExpansionHeight = getMinExpansionHeight();
1295 if (height < minExpansionHeight) {
1297 mClipRect.right = getWidth();
1299 mClipRect.bottom = (int) height;
1300 height = minExpansionHeight;
1301 setRequestedClipBounds(mClipRect);
1303 setRequestedClipBounds(null);
1307 float appearEndPosition = getAppearEndPosition();
1308 float appearStartPosition = getAppearStartPosition();
1309 float appearFraction = 1.0f;
1310 boolean appearing = height < appearEndPosition;
1311 mAmbientState.setAppearing(appearing);
1314 if (mShouldShowShelfOnly) {
1315 stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
1316 } else if (mQsExpanded) {
1317 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
1318 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
1319 if (stackStartPosition <= stackEndPosition) {
1320 stackHeight = stackEndPosition;
1322 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
1323 stackEndPosition, mQsExpansionFraction);
1326 stackHeight = (int) height;
1329 appearFraction = calculateAppearFraction(height);
1330 if (appearFraction >= 0) {
1331 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
1334 // This may happen when pushing up a heads up. We linearly push it up from the
1336 translationY = height - appearStartPosition + getExpandTranslationStart();
1338 if (isHeadsUpTransition()) {
1340 getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
1341 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
1343 stackHeight = (int) (height - translationY);
1346 if (stackHeight != mCurrentStackHeight) {
1347 mCurrentStackHeight = stackHeight;
1348 updateAlgorithmHeightAndPadding();
1349 requestChildrenUpdate();
1351 setStackTranslation(translationY);
1352 notifyAppearChangedListeners();
1355 private void notifyAppearChangedListeners() {
1358 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
1359 appear = calculateAppearFractionBypass();
1360 expandAmount = getPulseHeight();
1362 appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight));
1363 expandAmount = mExpandedHeight;
1365 if (appear != mLastSentAppear || expandAmount != mLastSentExpandedHeight) {
1366 mLastSentAppear = appear;
1367 mLastSentExpandedHeight = expandAmount;
1368 for (int i = 0; i < mExpandedHeightListeners.size(); i++) {
1369 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i);
1370 listener.accept(expandAmount, appear);
1375 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1376 private void setRequestedClipBounds(Rect clipRect) {
1377 mRequestedClipBounds = clipRect;
1382 * Return the height of the content ignoring the footer.
1384 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1385 public int getIntrinsicContentHeight() {
1386 return mIntrinsicContentHeight;
1389 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1390 public void updateClipping() {
1391 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
1392 && !mHeadsUpAnimatingAway;
1393 boolean clipToOutline = false;
1394 if (mIsClipped != clipped) {
1395 mIsClipped = clipped;
1398 if (mAmbientState.isHiddenAtAll()) {
1399 clipToOutline = true;
1400 invalidateOutline();
1401 if (isFullyHidden()) {
1402 setClipBounds(null);
1404 } else if (clipped) {
1405 setClipBounds(mRequestedClipBounds);
1407 setClipBounds(null);
1410 setClipToOutline(clipToOutline);
1414 * @return The translation at the beginning when expanding.
1415 * Measured relative to the resting position.
1417 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1418 private float getExpandTranslationStart() {
1419 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
1423 * @return the position from where the appear transition starts when expanding.
1424 * Measured in absolute height.
1426 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1427 private float getAppearStartPosition() {
1428 if (isHeadsUpTransition()) {
1429 return mHeadsUpInset
1430 + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
1432 return getMinExpansionHeight();
1436 * @return the height of the top heads up notification when pinned. This is different from the
1437 * intrinsic height, which also includes whether the notification is system expanded and
1438 * is mainly used when dragging down from a heads up notification.
1440 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1441 private int getTopHeadsUpPinnedHeight() {
1442 NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
1443 if (topEntry == null) {
1446 ExpandableNotificationRow row = topEntry.getRow();
1447 if (row.isChildInGroup()) {
1448 final NotificationEntry groupSummary
1449 = mGroupManager.getGroupSummary(row.getStatusBarNotification());
1450 if (groupSummary != null) {
1451 row = groupSummary.getRow();
1454 return row.getPinnedHeadsUpHeight();
1458 * @return the position from where the appear transition ends when expanding.
1459 * Measured in absolute height.
1461 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1462 private float getAppearEndPosition() {
1464 int notGoneChildCount = getNotGoneChildCount();
1465 if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) {
1466 if (isHeadsUpTransition()
1467 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) {
1468 appearPosition = getTopHeadsUpPinnedHeight();
1471 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) {
1472 appearPosition += mShelf.getIntrinsicHeight();
1476 appearPosition = mEmptyShadeView.getHeight();
1478 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
1481 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1482 private boolean isHeadsUpTransition() {
1483 NotificationSection firstVisibleSection = getFirstVisibleSection();
1484 return mTrackingHeadsUp && firstVisibleSection != null
1485 && firstVisibleSection.getFirstVisibleChild().isAboveShelf();
1489 * @param height the height of the panel
1490 * @return the fraction of the appear animation that has been performed
1492 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1493 public float calculateAppearFraction(float height) {
1494 float appearEndPosition = getAppearEndPosition();
1495 float appearStartPosition = getAppearStartPosition();
1496 return (height - appearStartPosition)
1497 / (appearEndPosition - appearStartPosition);
1500 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1501 public float getStackTranslation() {
1502 return mStackTranslation;
1505 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1506 private void setStackTranslation(float stackTranslation) {
1507 if (stackTranslation != mStackTranslation) {
1508 mStackTranslation = stackTranslation;
1509 mAmbientState.setStackTranslation(stackTranslation);
1510 requestChildrenUpdate();
1515 * Get the current height of the view. This is at most the msize of the view given by a the
1516 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
1518 * @return either the layout height or the externally defined height, whichever is smaller
1520 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1521 private int getLayoutHeight() {
1522 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
1525 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1526 public int getFirstItemMinHeight() {
1527 final ExpandableView firstChild = getFirstChildNotGone();
1528 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
1531 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1532 public void setQsContainer(ViewGroup qsContainer) {
1533 mQsContainer = qsContainer;
1536 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1537 public static boolean isPinnedHeadsUp(View v) {
1538 if (v instanceof ExpandableNotificationRow) {
1539 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1540 return row.isHeadsUp() && row.isPinned();
1545 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1546 private boolean isHeadsUp(View v) {
1547 if (v instanceof ExpandableNotificationRow) {
1548 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1549 return row.isHeadsUp();
1554 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1555 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
1556 getLocationOnScreen(mTempInt2);
1557 float localTouchY = touchY - mTempInt2[1];
1559 ExpandableView closestChild = null;
1560 float minDist = Float.MAX_VALUE;
1562 // find the view closest to the location, accounting for GONE views
1563 final int count = getChildCount();
1564 for (int childIdx = 0; childIdx < count; childIdx++) {
1565 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1566 if (slidingChild.getVisibility() == GONE
1567 || slidingChild instanceof StackScrollerDecorView) {
1570 float childTop = slidingChild.getTranslationY();
1571 float top = childTop + slidingChild.getClipTopAmount();
1572 float bottom = childTop + slidingChild.getActualHeight()
1573 - slidingChild.getClipBottomAmount();
1575 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1576 if (dist < minDist) {
1577 closestChild = slidingChild;
1581 return closestChild;
1584 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1585 private ExpandableView getChildAtPosition(float touchX, float touchY) {
1586 return getChildAtPosition(touchX, touchY, true /* requireMinHeight */);
1591 * Get the child at a certain screen location.
1593 * @param touchX the x coordinate
1594 * @param touchY the y coordinate
1595 * @param requireMinHeight Whether a minimum height is required for a child to be returned.
1596 * @return the child at the given location.
1598 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1599 private ExpandableView getChildAtPosition(float touchX, float touchY,
1600 boolean requireMinHeight) {
1601 // find the view under the pointer, accounting for GONE views
1602 final int count = getChildCount();
1603 for (int childIdx = 0; childIdx < count; childIdx++) {
1604 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1605 if (slidingChild.getVisibility() != VISIBLE
1606 || slidingChild instanceof StackScrollerDecorView) {
1609 float childTop = slidingChild.getTranslationY();
1610 float top = childTop + slidingChild.getClipTopAmount();
1611 float bottom = childTop + slidingChild.getActualHeight()
1612 - slidingChild.getClipBottomAmount();
1614 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1615 // camera affordance).
1617 int right = getWidth();
1619 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
1620 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
1621 if (slidingChild instanceof ExpandableNotificationRow) {
1622 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
1623 NotificationEntry entry = row.getEntry();
1624 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
1625 && mHeadsUpManager.getTopEntry().getRow() != row
1626 && mGroupManager.getGroupSummary(
1627 mHeadsUpManager.getTopEntry().notification)
1631 return row.getViewAtPosition(touchY - childTop);
1633 return slidingChild;
1639 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
1640 getLocationOnScreen(mTempInt2);
1641 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
1644 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1645 public void setScrollingEnabled(boolean enable) {
1646 mScrollingEnabled = enable;
1649 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1650 public void lockScrollTo(View v) {
1651 if (mForcedScroll == v) {
1658 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1659 public boolean scrollTo(View v) {
1660 ExpandableView expandableView = (ExpandableView) v;
1661 if (ANCHOR_SCROLLING) {
1664 int positionInLinearLayout = getPositionInLinearLayout(v);
1665 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1666 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
1668 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1669 // that it is not visible anymore.
1670 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1671 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1672 mDontReportNextOverScroll = true;
1681 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1684 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1685 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1686 return positionInLinearLayout + v.getIntrinsicHeight() +
1687 getImeInset() - getHeight()
1688 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
1692 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1693 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1694 mBottomInset = insets.getSystemWindowInsetBottom();
1696 if (ANCHOR_SCROLLING) {
1699 int range = getScrollRange();
1700 if (mOwnScrollY > range) {
1701 // HACK: We're repeatedly getting staggered insets here while the IME is
1702 // animating away. To work around that we'll wait until things have settled.
1703 removeCallbacks(mReclamp);
1704 postDelayed(mReclamp, 50);
1705 } else if (mForcedScroll != null) {
1706 // The scroll was requested before we got the actual inset - in case we need
1707 // to scroll up some more do so now.
1708 scrollTo(mForcedScroll);
1714 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1715 private Runnable mReclamp = new Runnable() {
1718 if (ANCHOR_SCROLLING) {
1721 int range = getScrollRange();
1722 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1724 mDontReportNextOverScroll = true;
1725 mDontClampNextScroll = true;
1730 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1731 public void setExpandingEnabled(boolean enable) {
1732 mExpandHelper.setEnabled(enable);
1735 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1736 private boolean isScrollingEnabled() {
1737 return mScrollingEnabled;
1740 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1741 private boolean onKeyguard() {
1742 return mStatusBarState == StatusBarState.KEYGUARD;
1746 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1747 protected void onConfigurationChanged(Configuration newConfig) {
1748 super.onConfigurationChanged(newConfig);
1749 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
1750 float densityScale = getResources().getDisplayMetrics().density;
1751 mSwipeHelper.setDensityScale(densityScale);
1752 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1753 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1754 initView(getContext());
1757 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1758 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
1759 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1760 true /* isDismissAll */);
1763 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1764 private void snapViewIfNeeded(NotificationEntry entry) {
1765 ExpandableNotificationRow child = entry.getRow();
1766 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
1767 // If the child is showing the notification menu snap to that
1768 if (child.getProvider() != null) {
1769 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
1770 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1775 @ShadeViewRefactor(RefactorComponent.ADAPTER)
1776 public ViewGroup getViewParentForNotification(NotificationEntry entry) {
1781 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1783 * @param deltaY The amount to scroll upwards, has to be positive.
1784 * @return The amount of scrolling to be performed by the scroller,
1785 * not handled by the overScroll amount.
1787 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1788 private float overScrollUp(int deltaY, int range) {
1789 deltaY = Math.max(deltaY, 0);
1790 float currentTopAmount = getCurrentOverScrollAmount(true);
1791 float newTopAmount = currentTopAmount - deltaY;
1792 if (currentTopAmount > 0) {
1793 setOverScrollAmount(newTopAmount, true /* onTop */,
1794 false /* animate */);
1796 // Top overScroll might not grab all scrolling motion,
1797 // we have to scroll as well.
1798 if (ANCHOR_SCROLLING) {
1799 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1800 // TODO: once we're recycling this will need to check the adapter position of the child
1801 ExpandableView lastRow = getLastRowNotGone();
1802 if (lastRow != null && !lastRow.isInShelf()) {
1803 float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
1804 if (scrollAmount > distanceToMax) {
1805 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1806 // We overScroll on the bottom
1807 setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
1809 false /* animate */);
1810 mScrollAnchorViewY -= distanceToMax;
1814 return scrollAmount;
1816 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1817 float newScrollY = mOwnScrollY + scrollAmount;
1818 if (newScrollY > range) {
1819 if (!mExpandedInThisMotion) {
1820 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1821 // We overScroll on the bottom
1822 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1824 false /* animate */);
1826 setOwnScrollY(range);
1827 scrollAmount = 0.0f;
1829 return scrollAmount;
1834 * Perform a scroll downward and adapt the overscroll amounts accordingly
1836 * @param deltaY The amount to scroll downwards, has to be negative.
1837 * @return The amount of scrolling to be performed by the scroller,
1838 * not handled by the overScroll amount.
1840 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1841 private float overScrollDown(int deltaY) {
1842 deltaY = Math.min(deltaY, 0);
1843 float currentBottomAmount = getCurrentOverScrollAmount(false);
1844 float newBottomAmount = currentBottomAmount + deltaY;
1845 if (currentBottomAmount > 0) {
1846 setOverScrollAmount(newBottomAmount, false /* onTop */,
1847 false /* animate */);
1849 // Bottom overScroll might not grab all scrolling motion,
1850 // we have to scroll as well.
1851 if (ANCHOR_SCROLLING) {
1852 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1853 // TODO: once we're recycling this will need to check the adapter position of the child
1854 ExpandableView firstChild = getFirstChildNotGone();
1855 float top = firstChild.getTranslationY();
1856 float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
1857 if (distanceToTop < -scrollAmount) {
1858 float currentTopPixels = getCurrentOverScrolledPixels(true);
1859 // We overScroll on the top
1860 setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
1862 false /* animate */);
1863 mScrollAnchorView = firstChild;
1864 mScrollAnchorViewY = 0;
1867 return scrollAmount;
1869 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1870 float newScrollY = mOwnScrollY + scrollAmount;
1871 if (newScrollY < 0) {
1872 float currentTopPixels = getCurrentOverScrolledPixels(true);
1873 // We overScroll on the top
1874 setOverScrolledPixels(currentTopPixels - newScrollY,
1876 false /* animate */);
1878 scrollAmount = 0.0f;
1880 return scrollAmount;
1884 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1885 private void initVelocityTrackerIfNotExists() {
1886 if (mVelocityTracker == null) {
1887 mVelocityTracker = VelocityTracker.obtain();
1891 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1892 private void recycleVelocityTracker() {
1893 if (mVelocityTracker != null) {
1894 mVelocityTracker.recycle();
1895 mVelocityTracker = null;
1899 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1900 private void initOrResetVelocityTracker() {
1901 if (mVelocityTracker == null) {
1902 mVelocityTracker = VelocityTracker.obtain();
1904 mVelocityTracker.clear();
1908 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1909 public void setFinishScrollingCallback(Runnable runnable) {
1910 mFinishScrollingCallback = runnable;
1913 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1914 private void animateScroll() {
1915 if (mScroller.computeScrollOffset()) {
1916 if (ANCHOR_SCROLLING) {
1917 int oldY = mLastScrollerY;
1918 int y = mScroller.getCurrY();
1919 int deltaY = y - oldY;
1921 int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
1922 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
1923 if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
1924 || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
1925 // This frame takes us into overscroll, so set the max overscroll based on
1926 // the current velocity
1927 setMaxOverScrollFromCurrentVelocity();
1929 customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
1933 int oldY = mOwnScrollY;
1934 int y = mScroller.getCurrY();
1937 int range = getScrollRange();
1938 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1939 // This frame takes us into overscroll, so set the max overscroll based on
1940 // the current velocity
1941 setMaxOverScrollFromCurrentVelocity();
1944 if (mDontClampNextScroll) {
1945 range = Math.max(range, oldY);
1947 customOverScrollBy(y - oldY, oldY, range,
1948 (int) (mMaxOverScroll));
1952 postOnAnimation(mReflingAndAnimateScroll);
1954 mDontClampNextScroll = false;
1955 if (mFinishScrollingCallback != null) {
1956 mFinishScrollingCallback.run();
1961 private void setMaxOverScrollFromCurrentVelocity() {
1962 float currVelocity = mScroller.getCurrVelocity();
1963 if (currVelocity >= mMinimumVelocity) {
1964 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1969 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
1970 * would cause us to exceed the provided maximum overscroll, springs back instead.
1972 * This method performs the determination of whether we're exceeding the overscroll and clamps
1973 * the scroll amount if so. The actual scrolling/overscrolling happens in
1974 * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
1975 * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
1977 * @param deltaY The (signed) number of pixels to scroll.
1978 * @param scrollY The current scroll position (absolute scrolling only).
1979 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
1980 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
1982 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
1983 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
1984 if (ANCHOR_SCROLLING) {
1985 boolean clampedY = false;
1987 int maxScrollAmount = getMaxNegativeScrollAmount();
1988 if (maxScrollAmount > Integer.MIN_VALUE) {
1989 maxScrollAmount -= maxOverScrollY;
1990 if (deltaY < maxScrollAmount) {
1991 deltaY = maxScrollAmount;
1996 int maxScrollAmount = getMaxPositiveScrollAmount();
1997 if (maxScrollAmount < Integer.MAX_VALUE) {
1998 maxScrollAmount += maxOverScrollY;
1999 if (deltaY > maxScrollAmount) {
2000 deltaY = maxScrollAmount;
2005 onCustomOverScrolledBy(deltaY, clampedY);
2007 int newScrollY = scrollY + deltaY;
2008 final int top = -maxOverScrollY;
2009 final int bottom = maxOverScrollY + scrollRangeY;
2011 boolean clampedY = false;
2012 if (newScrollY > bottom) {
2013 newScrollY = bottom;
2015 } else if (newScrollY < top) {
2020 onCustomOverScrolled(newScrollY, clampedY);
2025 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
2026 * overscroll effect based on numPixels. By default this will also cancel animations on the
2027 * same overScroll edge.
2029 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
2030 * the rubber-banding logic.
2031 * @param onTop Should the effect be applied on top of the scroller.
2032 * @param animate Should an animation be performed.
2034 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2035 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
2036 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
2040 * Set the effective overScroll amount which will be directly reflected in the layout.
2041 * By default this will also cancel animations on the same overScroll edge.
2043 * @param amount The amount to overScroll by.
2044 * @param onTop Should the effect be applied on top of the scroller.
2045 * @param animate Should an animation be performed.
2048 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2049 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
2050 setOverScrollAmount(amount, onTop, animate, true);
2054 * Set the effective overScroll amount which will be directly reflected in the layout.
2056 * @param amount The amount to overScroll by.
2057 * @param onTop Should the effect be applied on top of the scroller.
2058 * @param animate Should an animation be performed.
2059 * @param cancelAnimators Should running animations be cancelled.
2061 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2062 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2063 boolean cancelAnimators) {
2064 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
2068 * Set the effective overScroll amount which will be directly reflected in the layout.
2070 * @param amount The amount to overScroll by.
2071 * @param onTop Should the effect be applied on top of the scroller.
2072 * @param animate Should an animation be performed.
2073 * @param cancelAnimators Should running animations be cancelled.
2074 * @param isRubberbanded The value which will be passed to
2075 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
2077 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2078 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2079 boolean cancelAnimators, boolean isRubberbanded) {
2080 if (cancelAnimators) {
2081 mStateAnimator.cancelOverScrollAnimators(onTop);
2083 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
2086 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2087 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
2088 boolean isRubberbanded) {
2089 amount = Math.max(0, amount);
2091 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
2093 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
2094 mAmbientState.setOverScrollAmount(amount, onTop);
2096 notifyOverscrollTopListener(amount, isRubberbanded);
2098 requestChildrenUpdate();
2102 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2103 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
2104 mExpandHelper.onlyObserveMovements(amount > 1.0f);
2105 if (mDontReportNextOverScroll) {
2106 mDontReportNextOverScroll = false;
2109 if (mOverscrollTopChangedListener != null) {
2110 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
2114 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2115 public void setOverscrollTopChangedListener(
2116 OnOverscrollTopChangedListener overscrollTopChangedListener) {
2117 mOverscrollTopChangedListener = overscrollTopChangedListener;
2120 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2121 public float getCurrentOverScrollAmount(boolean top) {
2122 return mAmbientState.getOverScrollAmount(top);
2125 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2126 public float getCurrentOverScrolledPixels(boolean top) {
2127 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
2130 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2131 private void setOverScrolledPixels(float amount, boolean onTop) {
2133 mOverScrolledTopPixels = amount;
2135 mOverScrolledBottomPixels = amount;
2140 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
2141 * would cause us to exceed the provided maximum overscroll, springs back instead.
2143 * @param deltaY The (signed) number of pixels to scroll.
2144 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2145 * the overscroll limit.
2147 private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
2148 assert ANCHOR_SCROLLING;
2149 mScrollAnchorViewY -= deltaY;
2150 // Treat animating scrolls differently; see #computeScroll() for why.
2151 if (!mScroller.isFinished()) {
2155 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
2156 if (isScrolledToTop() && mScrollAnchorViewY > 0) {
2157 notifyOverscrollTopListener(mScrollAnchorViewY,
2158 isRubberbanded(true /* onTop */));
2160 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
2164 updateScrollAnchor();
2165 updateOnScrollChange();
2169 * Scrolls to the given position, overscrolling if needed. If called during a fling and the
2170 * position exceeds the provided maximum overscroll, springs back instead.
2172 * @param scrollY The target scroll position.
2173 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2174 * the overscroll limit.
2176 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2177 private void onCustomOverScrolled(int scrollY, boolean clampedY) {
2178 assert !ANCHOR_SCROLLING;
2179 // Treat animating scrolls differently; see #computeScroll() for why.
2180 if (!mScroller.isFinished()) {
2181 setOwnScrollY(scrollY);
2185 float overScrollTop = getCurrentOverScrollAmount(true);
2186 if (mOwnScrollY < 0) {
2187 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
2189 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
2193 setOwnScrollY(scrollY);
2198 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
2199 * overscroll amount back to zero.
2201 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2202 private void springBack() {
2203 if (ANCHOR_SCROLLING) {
2204 boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
2205 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
2206 boolean overscrolledBottom = maxPositiveScrollAmount < 0;
2207 if (overScrolledTop || overscrolledBottom) {
2209 if (overScrolledTop) {
2210 newAmount = mScrollAnchorViewY;
2211 mScrollAnchorViewY = 0;
2212 mDontReportNextOverScroll = true;
2214 newAmount = -maxPositiveScrollAmount;
2215 mScrollAnchorViewY -= maxPositiveScrollAmount;
2217 setOverScrollAmount(newAmount, overScrolledTop, false);
2218 setOverScrollAmount(0.0f, overScrolledTop, true);
2219 mScroller.forceFinished(true);
2222 int scrollRange = getScrollRange();
2223 boolean overScrolledTop = mOwnScrollY <= 0;
2224 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
2225 if (overScrolledTop || overScrolledBottom) {
2228 if (overScrolledTop) {
2230 newAmount = -mOwnScrollY;
2232 mDontReportNextOverScroll = true;
2235 newAmount = mOwnScrollY - scrollRange;
2236 setOwnScrollY(scrollRange);
2238 setOverScrollAmount(newAmount, onTop, false);
2239 setOverScrollAmount(0.0f, onTop, true);
2240 mScroller.forceFinished(true);
2245 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2246 private int getScrollRange() {
2247 // In current design, it only use the top HUN to treat all of HUNs
2248 // although there are more than one HUNs
2249 int contentHeight = mContentHeight;
2250 if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
2251 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
2253 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
2254 int imeInset = getImeInset();
2255 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
2259 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2260 private int getImeInset() {
2261 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
2265 * @return the first child which has visibility unequal to GONE
2267 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2268 public ExpandableView getFirstChildNotGone() {
2269 int childCount = getChildCount();
2270 for (int i = 0; i < childCount; i++) {
2271 View child = getChildAt(i);
2272 if (child.getVisibility() != View.GONE && child != mShelf) {
2273 return (ExpandableView) child;
2280 * @return the child before the given view which has visibility unequal to GONE
2282 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2283 public ExpandableView getViewBeforeView(ExpandableView view) {
2284 ExpandableView previousView = null;
2285 int childCount = getChildCount();
2286 for (int i = 0; i < childCount; i++) {
2287 View child = getChildAt(i);
2288 if (child == view) {
2289 return previousView;
2291 if (child.getVisibility() != View.GONE) {
2292 previousView = (ExpandableView) child;
2299 * @return The first child which has visibility unequal to GONE which is currently below the
2300 * given translationY or equal to it.
2302 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2303 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
2304 int childCount = getChildCount();
2305 for (int i = 0; i < childCount; i++) {
2306 View child = getChildAt(i);
2307 if (child.getVisibility() == View.GONE) {
2310 float rowTranslation = child.getTranslationY();
2311 if (rowTranslation >= translationY) {
2313 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
2314 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2315 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
2316 List<ExpandableNotificationRow> notificationChildren =
2317 row.getNotificationChildren();
2318 for (int childIndex = 0; childIndex < notificationChildren.size();
2320 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
2321 if (rowChild.getTranslationY() + rowTranslation >= translationY) {
2332 * @return the last child which has visibility unequal to GONE
2334 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2335 public ExpandableView getLastChildNotGone() {
2336 int childCount = getChildCount();
2337 for (int i = childCount - 1; i >= 0; i--) {
2338 View child = getChildAt(i);
2339 if (child.getVisibility() != View.GONE && child != mShelf) {
2340 return (ExpandableView) child;
2346 private ExpandableNotificationRow getLastRowNotGone() {
2347 int childCount = getChildCount();
2348 for (int i = childCount - 1; i >= 0; i--) {
2349 View child = getChildAt(i);
2350 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
2351 return (ExpandableNotificationRow) child;
2358 * @return the number of children which have visibility unequal to GONE
2360 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2361 public int getNotGoneChildCount() {
2362 int childCount = getChildCount();
2364 for (int i = 0; i < childCount; i++) {
2365 ExpandableView child = (ExpandableView) getChildAt(i);
2366 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
2373 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2374 private void updateContentHeight() {
2376 float previousPaddingRequest = mPaddingBetweenElements;
2377 float previousPaddingAmount = 0.0f;
2378 int numShownItems = 0;
2379 boolean finish = false;
2380 int maxDisplayedNotifications = mMaxDisplayedNotifications;
2382 for (int i = 0; i < getChildCount(); i++) {
2383 ExpandableView expandableView = (ExpandableView) getChildAt(i);
2384 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
2385 if (expandableView.getVisibility() != View.GONE
2386 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
2387 boolean limitReached = maxDisplayedNotifications != -1
2388 && numShownItems >= maxDisplayedNotifications;
2390 expandableView = mShelf;
2393 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
2395 if (increasedPaddingAmount >= 0.0f) {
2396 padding = (int) NotificationUtils.interpolate(
2397 previousPaddingRequest,
2398 mIncreasedPaddingBetweenElements,
2399 increasedPaddingAmount);
2400 previousPaddingRequest = (int) NotificationUtils.interpolate(
2401 mPaddingBetweenElements,
2402 mIncreasedPaddingBetweenElements,
2403 increasedPaddingAmount);
2405 int ownPadding = (int) NotificationUtils.interpolate(
2407 mPaddingBetweenElements,
2408 1.0f + increasedPaddingAmount);
2409 if (previousPaddingAmount > 0.0f) {
2410 padding = (int) NotificationUtils.interpolate(
2412 mIncreasedPaddingBetweenElements,
2413 previousPaddingAmount);
2415 padding = ownPadding;
2417 previousPaddingRequest = ownPadding;
2422 previousPaddingAmount = increasedPaddingAmount;
2423 height += expandableView.getIntrinsicHeight();
2430 mIntrinsicContentHeight = height;
2432 // The topPadding can be bigger than the regular padding when qs is expanded, in that
2433 // state the maxPanelHeight and the contentHeight should be bigger
2434 mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
2435 updateScrollability();
2436 clampScrollPosition();
2437 mAmbientState.setLayoutMaxHeight(mContentHeight);
2441 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2442 public boolean hasPulsingNotifications() {
2446 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2447 private void updateScrollability() {
2448 boolean scrollable = !mQsExpanded && getScrollRange() > 0;
2449 if (scrollable != mScrollable) {
2450 mScrollable = scrollable;
2451 setFocusable(scrollable);
2452 updateForwardAndBackwardScrollability();
2456 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2457 private void updateForwardAndBackwardScrollability() {
2458 boolean forwardScrollable = mScrollable && !isScrolledToBottom();
2459 boolean backwardsScrollable = mScrollable && !isScrolledToTop();
2460 boolean changed = forwardScrollable != mForwardScrollable
2461 || backwardsScrollable != mBackwardScrollable;
2462 mForwardScrollable = forwardScrollable;
2463 mBackwardScrollable = backwardsScrollable;
2465 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
2469 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2470 private void updateBackground() {
2471 // No need to update the background color if it's not being drawn.
2472 if (!mShouldDrawNotificationBackground) {
2476 updateBackgroundBounds();
2477 if (didSectionBoundsChange()) {
2478 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
2479 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
2480 if (!isExpanded()) {
2481 abortBackgroundAnimators();
2485 startBackgroundAnimation();
2487 for (NotificationSection section : mSections) {
2488 section.resetCurrentBounds();
2493 abortBackgroundAnimators();
2495 mAnimateNextBackgroundTop = false;
2496 mAnimateNextBackgroundBottom = false;
2497 mAnimateNextSectionBoundsChange = false;
2500 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2501 private void abortBackgroundAnimators() {
2502 for (NotificationSection section : mSections) {
2503 section.cancelAnimators();
2507 private boolean didSectionBoundsChange() {
2508 for (NotificationSection section : mSections) {
2509 if (section.didBoundsChange()) {
2516 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2517 private boolean areSectionBoundsAnimating() {
2518 for (NotificationSection section : mSections) {
2519 if (section.areBoundsAnimating()) {
2526 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2527 private void startBackgroundAnimation() {
2528 // TODO(kprevas): do we still need separate fields for top/bottom?
2529 // or can each section manage its own animation state?
2530 NotificationSection firstVisibleSection = getFirstVisibleSection();
2531 NotificationSection lastVisibleSection = getLastVisibleSection();
2532 for (NotificationSection section : mSections) {
2533 section.startBackgroundAnimation(
2534 section == firstVisibleSection
2535 ? mAnimateNextBackgroundTop
2536 : mAnimateNextSectionBoundsChange,
2537 section == lastVisibleSection
2538 ? mAnimateNextBackgroundBottom
2539 : mAnimateNextSectionBoundsChange);
2544 * Update the background bounds to the new desired bounds
2546 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2547 private void updateBackgroundBounds() {
2548 int left = mSidePaddings;
2549 int right = getWidth() - mSidePaddings;
2550 for (NotificationSection section : mSections) {
2551 section.getBounds().left = left;
2552 section.getBounds().right = right;
2556 for (NotificationSection section : mSections) {
2557 section.getBounds().top = 0;
2558 section.getBounds().bottom = 0;
2563 NotificationSection lastSection = getLastVisibleSection();
2564 boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
2566 minTopPosition = (int) (mTopPadding + mStackTranslation);
2567 } else if (lastSection == null) {
2568 minTopPosition = mTopPadding;
2570 // The first sections could be empty while there could still be elements in later
2571 // sections. The position of these first few sections is determined by the position of
2572 // the first visible section.
2573 NotificationSection firstVisibleSection = getFirstVisibleSection();
2574 firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
2575 false /* shiftPulsingWithFirst */);
2576 minTopPosition = firstVisibleSection.getBounds().top;
2578 boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1
2579 && (mAmbientState.isDozing()
2580 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard));
2581 for (NotificationSection section : mSections) {
2582 int minBottomPosition = minTopPosition;
2583 if (section == lastSection) {
2584 // We need to make sure the section goes all the way to the shelf
2585 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
2586 + mShelf.getIntrinsicHeight());
2588 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
2589 shiftPulsingWithFirst);
2590 shiftPulsingWithFirst = false;
2594 private NotificationSection getFirstVisibleSection() {
2595 for (NotificationSection section : mSections) {
2596 if (section.getFirstVisibleChild() != null) {
2603 private NotificationSection getLastVisibleSection() {
2604 for (int i = mSections.length - 1; i >= 0; i--) {
2605 NotificationSection section = mSections[i];
2606 if (section.getLastVisibleChild() != null) {
2613 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2614 private ActivatableNotificationView getLastChildWithBackground() {
2615 int childCount = getChildCount();
2616 for (int i = childCount - 1; i >= 0; i--) {
2617 View child = getChildAt(i);
2618 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2619 && child != mShelf) {
2620 return (ActivatableNotificationView) child;
2626 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2627 private ActivatableNotificationView getFirstChildWithBackground() {
2628 int childCount = getChildCount();
2629 for (int i = 0; i < childCount; i++) {
2630 View child = getChildAt(i);
2631 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2632 && child != mShelf) {
2633 return (ActivatableNotificationView) child;
2640 * Fling the scroll view
2642 * @param velocityY The initial velocity in the Y direction. Positive
2643 * numbers mean that the finger/cursor is moving down the screen,
2644 * which means we want to scroll towards the top.
2646 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2647 protected void fling(int velocityY) {
2648 if (getChildCount() > 0) {
2649 float topAmount = getCurrentOverScrollAmount(true);
2650 float bottomAmount = getCurrentOverScrollAmount(false);
2651 if (velocityY < 0 && topAmount > 0) {
2652 if (ANCHOR_SCROLLING) {
2653 mScrollAnchorViewY += topAmount;
2655 setOwnScrollY(mOwnScrollY - (int) topAmount);
2657 mDontReportNextOverScroll = true;
2658 setOverScrollAmount(0, true, false);
2659 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
2660 * mOverflingDistance + topAmount;
2661 } else if (velocityY > 0 && bottomAmount > 0) {
2662 if (ANCHOR_SCROLLING) {
2663 mScrollAnchorViewY -= bottomAmount;
2665 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
2667 setOverScrollAmount(0, false, false);
2668 mMaxOverScroll = Math.abs(velocityY) / 1000f
2669 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
2672 // it will be set once we reach the boundary
2673 mMaxOverScroll = 0.0f;
2675 if (ANCHOR_SCROLLING) {
2676 flingScroller(velocityY);
2678 int scrollRange = getScrollRange();
2679 int minScrollY = Math.max(0, scrollRange);
2680 if (mExpandedInThisMotion) {
2681 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2683 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
2684 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
2692 * Flings the overscroller with the given velocity (anchor-based scrolling).
2694 * Because anchor-based scrolling can't track the current scroll position, the overscroller is
2695 * always started at startY = 0, and we interpret the positions it computes as relative to the
2696 * start of the scroll.
2698 private void flingScroller(int velocityY) {
2699 assert ANCHOR_SCROLLING;
2700 mIsScrollerBoundSet = false;
2701 maybeFlingScroller(velocityY, true /* always fling */);
2704 private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
2705 assert ANCHOR_SCROLLING;
2706 // Attempt to determine the maximum amount to scroll before we reach the end.
2707 // If the first view is not materialized (for an upwards scroll) or the last view is either
2708 // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
2709 // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
2710 // the scroller once we approach the start/end of the list.
2711 int minY = Integer.MIN_VALUE;
2712 int maxY = Integer.MAX_VALUE;
2713 if (velocityY < 0) {
2714 minY = getMaxNegativeScrollAmount();
2715 if (minY > Integer.MIN_VALUE) {
2716 mIsScrollerBoundSet = true;
2719 maxY = getMaxPositiveScrollAmount();
2720 if (maxY < Integer.MAX_VALUE) {
2721 mIsScrollerBoundSet = true;
2724 if (mIsScrollerBoundSet || alwaysFling) {
2726 // x velocity is set to 1 to avoid overscroller bug
2727 mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
2728 mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
2733 * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
2734 * before reaching the bottom of the list (discounting overscroll).
2736 * If the return value is negative then we have overscrolled; this is a transient state which
2737 * should immediately be handled by adjusting the anchor position and adding the extra space to
2738 * the bottom overscroll amount.
2740 * If we don't know how many pixels we have left to scroll (because the last row has not been
2741 * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
2742 * {@link Integer#MAX_VALUE}.
2744 private int getMaxPositiveScrollAmount() {
2745 assert ANCHOR_SCROLLING;
2746 // TODO: once we're recycling we need to check the adapter position of the last child.
2747 ExpandableNotificationRow lastRow = getLastRowNotGone();
2748 if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
2749 // distance from bottom of last child to bottom of notifications area is:
2750 // distance from bottom of last child
2751 return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
2752 // to top of anchor view
2753 - mScrollAnchorView.getTranslationY()
2754 // plus distance from anchor view to top of notifications area
2755 + mScrollAnchorViewY
2756 // minus height of notifications area.
2757 - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
2759 return Integer.MAX_VALUE;
2764 * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
2765 * direction (upwards) before reaching the top of the list (discounting overscroll).
2767 * If the return value is positive then we have overscrolled; this is a transient state which
2768 * should immediately be handled by adjusting the anchor position and adding the extra space to
2769 * the top overscroll amount.
2771 * If we don't know how many pixels we have left to scroll (because the first row has not been
2772 * materialized), we return {@link Integer#MIN_VALUE}.
2774 private int getMaxNegativeScrollAmount() {
2775 assert ANCHOR_SCROLLING;
2776 // TODO: once we're recycling we need to check the adapter position of the first child.
2777 ExpandableView firstChild = getFirstChildNotGone();
2778 if (mScrollAnchorView != null && firstChild != null) {
2779 // distance from top of first child to top of notifications area is:
2780 // distance from top of anchor view
2781 return (int) -(mScrollAnchorView.getTranslationY()
2782 // to top of first child
2783 - firstChild.getTranslationY()
2784 // minus distance from top of anchor view to top of notifications area.
2785 - mScrollAnchorViewY);
2787 return Integer.MIN_VALUE;
2792 * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
2793 * not being materialized or being pinned to the shelf, we need to check on every frame if we're
2794 * able to set the bounds. If we are, we fling the scroller again with the newly computed
2797 private void maybeReflingScroller() {
2798 if (!mIsScrollerBoundSet) {
2799 // Because mScroller is a flywheel scroller, we fling with the minimum possible
2800 // velocity to establish direction, so as not to perceptibly affect the velocity.
2801 maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
2802 false /* alwaysFling */);
2807 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2808 * overScroll view (i.e QS).
2810 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2811 private boolean shouldOverScrollFling(int initialVelocity) {
2812 float topOverScroll = getCurrentOverScrollAmount(true);
2813 return mScrolledToTopOnFirstDown
2814 && !mExpandedInThisMotion
2815 && topOverScroll > mMinTopOverScrollToEscape
2816 && initialVelocity > 0;
2820 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2823 * @param qsHeight the top padding imposed by the quick settings panel
2824 * @param animate whether to animate the change
2826 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2827 public void updateTopPadding(float qsHeight, boolean animate) {
2828 int topPadding = (int) qsHeight;
2829 int minStackHeight = getLayoutMinHeight();
2830 if (topPadding + minStackHeight > getHeight()) {
2831 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
2833 mTopPaddingOverflow = 0;
2835 setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled());
2836 setExpandedHeight(mExpandedHeight);
2839 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2840 public void setMaxTopPadding(int maxTopPadding) {
2841 mMaxTopPadding = maxTopPadding;
2844 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2845 public int getLayoutMinHeight() {
2846 if (isHeadsUpTransition()) {
2847 return getTopHeadsUpPinnedHeight();
2849 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
2852 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2853 public float getTopPaddingOverflow() {
2854 return mTopPaddingOverflow;
2857 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2858 public int getPeekHeight() {
2859 final ExpandableView firstChild = getFirstChildNotGone();
2860 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
2862 int shelfHeight = 0;
2863 if (getLastVisibleSection() != null && mShelf.getVisibility() != GONE) {
2864 shelfHeight = mShelf.getIntrinsicHeight();
2866 return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
2869 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2870 private int clampPadding(int desiredPadding) {
2871 return Math.max(desiredPadding, mIntrinsicPadding);
2874 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2875 private float getRubberBandFactor(boolean onTop) {
2877 return RUBBER_BAND_FACTOR_NORMAL;
2879 if (mExpandedInThisMotion) {
2880 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
2881 } else if (mIsExpansionChanging || mPanelTracking) {
2882 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2883 } else if (mScrolledToTopOnFirstDown) {
2886 return RUBBER_BAND_FACTOR_NORMAL;
2890 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2891 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2892 * overscroll view (e.g. expand QS).
2894 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2895 private boolean isRubberbanded(boolean onTop) {
2896 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
2897 || !mScrolledToTopOnFirstDown;
2902 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2903 public void setChildTransferInProgress(boolean childTransferInProgress) {
2904 Assert.isMainThread();
2905 mChildTransferInProgress = childTransferInProgress;
2908 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
2910 public void onViewRemoved(View child) {
2911 super.onViewRemoved(child);
2912 // we only call our internal methods if this is actually a removal and not just a
2913 // notification which becomes a child notification
2914 if (!mChildTransferInProgress) {
2915 onViewRemovedInternal((ExpandableView) child, this);
2919 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2921 public void cleanUpViewStateForEntry(NotificationEntry entry) {
2922 View child = entry.getRow();
2923 if (child == mSwipeHelper.getTranslatingParentView()) {
2924 mSwipeHelper.clearTranslatingParentView();
2928 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
2929 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) {
2930 if (mChangePositionInProgress) {
2931 // This is only a position change, don't do anything special
2934 child.setOnHeightChangedListener(null);
2935 updateScrollStateForRemovedChild(child);
2936 boolean animationGenerated = generateRemoveAnimation(child);
2937 if (animationGenerated) {
2938 if (!mSwipedOutViews.contains(child)
2939 || Math.abs(child.getTranslation()) != child.getWidth()) {
2940 container.addTransientView(child, 0);
2941 child.setTransientContainer(container);
2944 mSwipedOutViews.remove(child);
2946 updateAnimationState(false, child);
2948 focusNextViewIfFocused(child);
2951 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2952 private void focusNextViewIfFocused(View view) {
2953 if (view instanceof ExpandableNotificationRow) {
2954 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2955 if (row.shouldRefocusOnDismiss()) {
2956 View nextView = row.getChildAfterViewWhenDismissed();
2957 if (nextView == null) {
2958 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2959 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2960 ? groupParentWhenDismissed.getTranslationY()
2961 : view.getTranslationY(), true /* ignoreChildren */);
2963 if (nextView != null) {
2964 nextView.requestAccessibilityFocus();
2971 @ShadeViewRefactor(RefactorComponent.ADAPTER)
2972 private boolean isChildInGroup(View child) {
2973 return child instanceof ExpandableNotificationRow
2974 && mGroupManager.isChildInGroupWithSummary(
2975 ((ExpandableNotificationRow) child).getStatusBarNotification());
2979 * Generate a remove animation for a child view.
2981 * @param child The view to generate the remove animation for.
2982 * @return Whether an animation was generated.
2984 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
2985 private boolean generateRemoveAnimation(ExpandableView child) {
2986 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
2987 mAddedHeadsUpChildren.remove(child);
2990 if (isClickedHeadsUp(child)) {
2991 // An animation is already running, add it transiently
2992 mClearTransientViewsWhenFinished.add(child);
2995 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
2996 if (!mChildrenToAddAnimated.contains(child)) {
2997 // Generate Animations
2998 mChildrenToRemoveAnimated.add(child);
2999 mNeedsAnimation = true;
3002 mChildrenToAddAnimated.remove(child);
3003 mFromMoreCardAdditions.remove(child);
3010 @ShadeViewRefactor(RefactorComponent.ADAPTER)
3011 private boolean isClickedHeadsUp(View child) {
3012 return HeadsUpUtil.isClickedHeadsUpNotification(child);
3016 * Remove a removed child view from the heads up animations if it was just added there
3018 * @return whether any child was removed from the list to animate
3020 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3021 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
3022 boolean hasAddEvent = false;
3023 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
3024 ExpandableNotificationRow row = eventPair.first;
3025 boolean isHeadsUp = eventPair.second;
3027 mTmpList.add(eventPair);
3028 hasAddEvent |= isHeadsUp;
3032 // This child was just added lets remove all events.
3033 mHeadsUpChangeAnimations.removeAll(mTmpList);
3034 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
3041 * @param child the child to query
3042 * @return whether a view is not a top level child but a child notification and that group is
3045 @ShadeViewRefactor(RefactorComponent.ADAPTER)
3046 private boolean isChildInInvisibleGroup(View child) {
3047 if (child instanceof ExpandableNotificationRow) {
3048 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3049 NotificationEntry groupSummary =
3050 mGroupManager.getGroupSummary(row.getStatusBarNotification());
3051 if (groupSummary != null && groupSummary.getRow() != row) {
3052 return row.getVisibility() == View.INVISIBLE;
3059 * Updates the scroll position when a child was removed
3061 * @param removedChild the removed child
3063 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3064 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
3065 if (ANCHOR_SCROLLING) {
3066 if (removedChild == mScrollAnchorView) {
3067 ExpandableView firstChild = getFirstChildNotGone();
3068 if (firstChild != null) {
3069 mScrollAnchorView = firstChild;
3071 mScrollAnchorView = mShelf;
3073 // Adjust anchor view Y by the distance between the old and new anchors
3074 // so that there's no visible change.
3075 mScrollAnchorViewY +=
3076 mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
3078 updateScrollAnchor();
3079 // TODO: once we're recycling this will need to check the adapter position of the child
3080 if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
3081 mScrollAnchorViewY = 0;
3083 updateOnScrollChange();
3085 int startingPosition = getPositionInLinearLayout(removedChild);
3086 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
3088 if (increasedPaddingAmount >= 0) {
3089 padding = (int) NotificationUtils.interpolate(
3090 mPaddingBetweenElements,
3091 mIncreasedPaddingBetweenElements,
3092 increasedPaddingAmount);
3094 padding = (int) NotificationUtils.interpolate(
3096 mPaddingBetweenElements,
3097 1.0f + increasedPaddingAmount);
3099 int childHeight = getIntrinsicHeight(removedChild) + padding;
3100 int endPosition = startingPosition + childHeight;
3101 if (endPosition <= mOwnScrollY) {
3102 // This child is fully scrolled of the top, so we have to deduct its height from the
3104 setOwnScrollY(mOwnScrollY - childHeight);
3105 } else if (startingPosition < mOwnScrollY) {
3106 // This child is currently being scrolled into, set the scroll position to the
3107 // start of this child
3108 setOwnScrollY(startingPosition);
3113 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
3114 private int getIntrinsicHeight(View view) {
3115 if (view instanceof ExpandableView) {
3116 ExpandableView expandableView = (ExpandableView) view;
3117 return expandableView.getIntrinsicHeight();
3119 return view.getHeight();
3122 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
3123 public int getPositionInLinearLayout(View requestedView) {
3124 ExpandableNotificationRow childInGroup = null;
3125 ExpandableNotificationRow requestedRow = null;
3126 if (isChildInGroup(requestedView)) {
3127 // We're asking for a child in a group. Calculate the position of the parent first,
3128 // then within the parent.
3129 childInGroup = (ExpandableNotificationRow) requestedView;
3130 requestedView = requestedRow = childInGroup.getNotificationParent();
3133 float previousPaddingRequest = mPaddingBetweenElements;
3134 float previousPaddingAmount = 0.0f;
3135 for (int i = 0; i < getChildCount(); i++) {
3136 ExpandableView child = (ExpandableView) getChildAt(i);
3137 boolean notGone = child.getVisibility() != View.GONE;
3138 if (notGone && !child.hasNoContentHeight()) {
3139 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
3141 if (increasedPaddingAmount >= 0.0f) {
3142 padding = (int) NotificationUtils.interpolate(
3143 previousPaddingRequest,
3144 mIncreasedPaddingBetweenElements,
3145 increasedPaddingAmount);
3146 previousPaddingRequest = (int) NotificationUtils.interpolate(
3147 mPaddingBetweenElements,
3148 mIncreasedPaddingBetweenElements,
3149 increasedPaddingAmount);
3151 int ownPadding = (int) NotificationUtils.interpolate(
3153 mPaddingBetweenElements,
3154 1.0f + increasedPaddingAmount);
3155 if (previousPaddingAmount > 0.0f) {
3156 padding = (int) NotificationUtils.interpolate(
3158 mIncreasedPaddingBetweenElements,
3159 previousPaddingAmount);
3161 padding = ownPadding;
3163 previousPaddingRequest = ownPadding;
3165 if (position != 0) {
3166 position += padding;
3168 previousPaddingAmount = increasedPaddingAmount;
3170 if (child == requestedView) {
3171 if (requestedRow != null) {
3172 position += requestedRow.getPositionOfChild(childInGroup);
3177 position += getIntrinsicHeight(child);
3184 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3185 public void onViewAdded(View child) {
3186 super.onViewAdded(child);
3187 onViewAddedInternal((ExpandableView) child);
3190 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3191 private void updateFirstAndLastBackgroundViews() {
3192 NotificationSection firstSection = getFirstVisibleSection();
3193 NotificationSection lastSection = getLastVisibleSection();
3194 ActivatableNotificationView previousFirstChild =
3195 firstSection == null ? null : firstSection.getFirstVisibleChild();
3196 ActivatableNotificationView previousLastChild =
3197 lastSection == null ? null : lastSection.getLastVisibleChild();
3199 ActivatableNotificationView firstChild = getFirstChildWithBackground();
3200 ActivatableNotificationView lastChild = getLastChildWithBackground();
3201 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
3202 mSections[0], mSections[1], firstChild, lastChild);
3204 if (mAnimationsEnabled && mIsExpanded) {
3205 mAnimateNextBackgroundTop = firstChild != previousFirstChild;
3206 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
3207 mAnimateNextSectionBoundsChange = sectionViewsChanged;
3209 mAnimateNextBackgroundTop = false;
3210 mAnimateNextBackgroundBottom = false;
3211 mAnimateNextSectionBoundsChange = false;
3213 mAmbientState.setLastVisibleBackgroundChild(lastChild);
3214 mRoundnessManager.updateRoundedChildren(mSections);
3215 mAnimateBottomOnLayout = false;
3219 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
3220 private void onViewAddedInternal(ExpandableView child) {
3221 updateHideSensitiveForChild(child);
3222 child.setOnHeightChangedListener(this);
3223 generateAddAnimation(child, false /* fromMoreCard */);
3224 updateAnimationState(child);
3225 updateChronometerForChild(child);
3226 if (child instanceof ExpandableNotificationRow) {
3227 ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
3229 if (ANCHOR_SCROLLING) {
3230 // TODO: once we're recycling this will need to check the adapter position of the child
3231 if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
3232 // New child was added at the top while we're scrolled to the top;
3233 // make it the new anchor view so that we stay at the top.
3234 mScrollAnchorView = child;
3239 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
3240 private void updateHideSensitiveForChild(ExpandableView child) {
3241 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
3245 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3246 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
3247 onViewRemovedInternal(row, childrenContainer);
3251 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3252 public void notifyGroupChildAdded(ExpandableView row) {
3253 onViewAddedInternal(row);
3256 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3257 public void setAnimationsEnabled(boolean animationsEnabled) {
3258 mAnimationsEnabled = animationsEnabled;
3259 updateNotificationAnimationStates();
3260 if (!animationsEnabled) {
3261 mSwipedOutViews.clear();
3262 mChildrenToRemoveAnimated.clear();
3263 clearTemporaryViewsInGroup(this);
3267 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3268 private void updateNotificationAnimationStates() {
3269 boolean running = mAnimationsEnabled || hasPulsingNotifications();
3270 mShelf.setAnimationsEnabled(running);
3271 int childCount = getChildCount();
3272 for (int i = 0; i < childCount; i++) {
3273 View child = getChildAt(i);
3274 running &= mIsExpanded || isPinnedHeadsUp(child);
3275 updateAnimationState(running, child);
3279 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3280 private void updateAnimationState(View child) {
3281 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
3282 && (mIsExpanded || isPinnedHeadsUp(child)), child);
3286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3287 public void setExpandingNotification(ExpandableNotificationRow row) {
3288 mAmbientState.setExpandingNotification(row);
3289 requestChildrenUpdate();
3293 @ShadeViewRefactor(RefactorComponent.ADAPTER)
3294 public void bindRow(ExpandableNotificationRow row) {
3295 row.setHeadsUpAnimatingAwayListener(animatingAway -> {
3296 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
3297 mHeadsUpAppearanceController.updateHeader(row.getEntry());
3302 public boolean containsView(View v) {
3303 return v.getParent() == this;
3307 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3308 public void applyExpandAnimationParams(ExpandAnimationParameters params) {
3309 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
3310 requestChildrenUpdate();
3313 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3314 private void updateAnimationState(boolean running, View child) {
3315 if (child instanceof ExpandableNotificationRow) {
3316 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3317 row.setIconAnimationRunning(running);
3321 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3322 public boolean isAddOrRemoveAnimationPending() {
3323 return mNeedsAnimation
3324 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
3328 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3329 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
3330 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
3331 // Generate Animations
3332 mChildrenToAddAnimated.add(child);
3334 mFromMoreCardAdditions.add(child);
3336 mNeedsAnimation = true;
3338 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress
3339 && !isFullyHidden()) {
3340 mAddedHeadsUpChildren.add(child);
3341 mChildrenToAddAnimated.remove(child);
3346 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3347 public void changeViewPosition(ExpandableView child, int newIndex) {
3348 Assert.isMainThread();
3349 if (mChangePositionInProgress) {
3350 throw new IllegalStateException("Reentrant call to changeViewPosition");
3353 int currentIndex = indexOfChild(child);
3355 if (currentIndex == -1) {
3356 boolean isTransient = false;
3357 if (child instanceof ExpandableNotificationRow
3358 && ((ExpandableNotificationRow) child).getTransientContainer() != null) {
3361 Log.e(TAG, "Attempting to re-position "
3362 + (isTransient ? "transient" : "")
3369 if (child != null && child.getParent() == this && currentIndex != newIndex) {
3370 mChangePositionInProgress = true;
3371 ((ExpandableView) child).setChangingPosition(true);
3373 addView(child, newIndex);
3374 ((ExpandableView) child).setChangingPosition(false);
3375 mChangePositionInProgress = false;
3376 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
3377 mChildrenChangingPositions.add(child);
3378 mNeedsAnimation = true;
3383 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3384 private void startAnimationToState() {
3385 if (mNeedsAnimation) {
3386 generateAllAnimationEvents();
3387 mNeedsAnimation = false;
3389 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
3390 setAnimationRunning(true);
3391 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
3392 mAnimationEvents.clear();
3394 updateViewShadows();
3395 updateClippingToTopRoundedCorner();
3397 applyCurrentState();
3399 mGoToFullShadeDelay = 0;
3402 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3403 private void generateAllAnimationEvents() {
3404 generateHeadsUpAnimationEvents();
3405 generateChildRemovalEvents();
3406 generateChildAdditionEvents();
3407 generatePositionChangeEvents();
3408 generateTopPaddingEvent();
3409 generateActivateEvent();
3410 generateDimmedEvent();
3411 generateHideSensitiveEvent();
3412 generateGoToFullShadeEvent();
3413 generateViewResizeEvent();
3414 generateGroupExpansionEvent();
3415 generateAnimateEverythingEvent();
3418 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3419 private void generateHeadsUpAnimationEvents() {
3420 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
3421 ExpandableNotificationRow row = eventPair.first;
3422 boolean isHeadsUp = eventPair.second;
3423 if (isHeadsUp != row.isHeadsUp()) {
3424 // For cases where we have a heads up showing and appearing again we shouldn't
3425 // do the animations at all.
3428 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
3429 boolean onBottom = false;
3430 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
3431 boolean performDisappearAnimation = !mIsExpanded
3432 // Only animate if we still have pinned heads up, otherwise we just have the
3433 // regular collapse animation of the lock screen
3434 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard()
3435 && mHeadsUpManager.hasPinnedHeadsUp());
3436 if (performDisappearAnimation && !isHeadsUp) {
3437 type = row.wasJustClicked()
3438 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3439 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
3440 if (row.isChildInGroup()) {
3441 // We can otherwise get stuck in there if it was just isolated
3442 row.setHeadsUpAnimatingAway(false);
3446 ExpandableViewState viewState = row.getViewState();
3447 if (viewState == null) {
3448 // A view state was never generated for this view, so we don't need to animate
3449 // this. This may happen with notification children.
3452 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
3453 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
3454 // Our custom add animation
3455 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
3457 // Normal add animation
3458 type = AnimationEvent.ANIMATION_TYPE_ADD;
3460 onBottom = !pinnedAndClosed;
3463 AnimationEvent event = new AnimationEvent(row, type);
3464 event.headsUpFromBottom = onBottom;
3465 mAnimationEvents.add(event);
3467 mHeadsUpChangeAnimations.clear();
3468 mAddedHeadsUpChildren.clear();
3471 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
3472 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
3473 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
3479 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3480 private void generateGroupExpansionEvent() {
3481 // Generate a group expansion/collapsing event if there is such a group at all
3482 if (mExpandedGroupView != null) {
3483 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
3484 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
3485 mExpandedGroupView = null;
3489 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3490 private void generateViewResizeEvent() {
3491 if (mNeedViewResizeAnimation) {
3492 boolean hasDisappearAnimation = false;
3493 for (AnimationEvent animationEvent : mAnimationEvents) {
3494 final int type = animationEvent.animationType;
3495 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3496 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
3497 hasDisappearAnimation = true;
3502 if (!hasDisappearAnimation) {
3503 mAnimationEvents.add(
3504 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
3507 mNeedViewResizeAnimation = false;
3510 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3511 private void generateChildRemovalEvents() {
3512 for (ExpandableView child : mChildrenToRemoveAnimated) {
3513 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
3515 // we need to know the view after this one
3516 float removedTranslation = child.getTranslationY();
3517 boolean ignoreChildren = true;
3518 if (child instanceof ExpandableNotificationRow) {
3519 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3520 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
3521 removedTranslation = row.getTranslationWhenRemoved();
3522 ignoreChildren = false;
3524 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
3526 if (!childWasSwipedOut) {
3527 Rect clipBounds = child.getClipBounds();
3528 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
3530 if (childWasSwipedOut && child instanceof ExpandableView) {
3531 // Clean up any potential transient views if the child has already been swiped
3532 // out, as we won't be animating it further (due to its height already being
3534 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
3535 if (transientContainer != null) {
3536 transientContainer.removeTransientView(child);
3540 int animationType = childWasSwipedOut
3541 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
3542 : AnimationEvent.ANIMATION_TYPE_REMOVE;
3543 AnimationEvent event = new AnimationEvent(child, animationType);
3544 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
3546 mAnimationEvents.add(event);
3547 mSwipedOutViews.remove(child);
3549 mChildrenToRemoveAnimated.clear();
3552 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3553 private void generatePositionChangeEvents() {
3554 for (ExpandableView child : mChildrenChangingPositions) {
3555 mAnimationEvents.add(new AnimationEvent(child,
3556 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3558 mChildrenChangingPositions.clear();
3559 if (mGenerateChildOrderChangedEvent) {
3560 mAnimationEvents.add(new AnimationEvent(null,
3561 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3562 mGenerateChildOrderChangedEvent = false;
3566 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3567 private void generateChildAdditionEvents() {
3568 for (ExpandableView child : mChildrenToAddAnimated) {
3569 if (mFromMoreCardAdditions.contains(child)) {
3570 mAnimationEvents.add(new AnimationEvent(child,
3571 AnimationEvent.ANIMATION_TYPE_ADD,
3572 StackStateAnimator.ANIMATION_DURATION_STANDARD));
3574 mAnimationEvents.add(new AnimationEvent(child,
3575 AnimationEvent.ANIMATION_TYPE_ADD));
3578 mChildrenToAddAnimated.clear();
3579 mFromMoreCardAdditions.clear();
3582 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3583 private void generateTopPaddingEvent() {
3584 if (mTopPaddingNeedsAnimation) {
3585 AnimationEvent event;
3586 if (mAmbientState.isDozing()) {
3587 event = new AnimationEvent(null /* view */,
3588 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED,
3589 KeyguardSliceView.DEFAULT_ANIM_DURATION);
3591 event = new AnimationEvent(null /* view */,
3592 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED);
3594 mAnimationEvents.add(event);
3596 mTopPaddingNeedsAnimation = false;
3599 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3600 private void generateActivateEvent() {
3601 if (mActivateNeedsAnimation) {
3602 mAnimationEvents.add(
3603 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
3605 mActivateNeedsAnimation = false;
3608 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3609 private void generateAnimateEverythingEvent() {
3610 if (mEverythingNeedsAnimation) {
3611 mAnimationEvents.add(
3612 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
3614 mEverythingNeedsAnimation = false;
3617 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3618 private void generateDimmedEvent() {
3619 if (mDimmedNeedsAnimation) {
3620 mAnimationEvents.add(
3621 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
3623 mDimmedNeedsAnimation = false;
3626 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3627 private void generateHideSensitiveEvent() {
3628 if (mHideSensitiveNeedsAnimation) {
3629 mAnimationEvents.add(
3630 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
3632 mHideSensitiveNeedsAnimation = false;
3635 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
3636 private void generateGoToFullShadeEvent() {
3637 if (mGoToFullShadeNeedsAnimation) {
3638 mAnimationEvents.add(
3639 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
3641 mGoToFullShadeNeedsAnimation = false;
3644 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
3645 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
3646 return new StackScrollAlgorithm(context, this);
3650 * @return Whether a y coordinate is inside the content.
3652 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3653 public boolean isInContentBounds(float y) {
3654 return y < getHeight() - getEmptyBottomMargin();
3657 @ShadeViewRefactor(RefactorComponent.INPUT)
3658 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
3659 mLongPressListener = listener;
3663 @ShadeViewRefactor(RefactorComponent.INPUT)
3664 public boolean onTouchEvent(MotionEvent ev) {
3665 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
3666 || ev.getActionMasked() == MotionEvent.ACTION_UP;
3667 handleEmptySpaceClick(ev);
3668 boolean expandWantsIt = false;
3669 boolean swipingInProgress = mSwipingInProgress;
3670 if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
3672 mExpandHelper.onlyObserveMovements(false);
3674 boolean wasExpandingBefore = mExpandingNotification;
3675 expandWantsIt = mExpandHelper.onTouchEvent(ev);
3676 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
3677 && !mDisallowScrollingInThisMotion) {
3678 dispatchDownEventToScroller(ev);
3681 boolean scrollerWantsIt = false;
3682 if (mIsExpanded && !swipingInProgress && !mExpandingNotification
3683 && !mDisallowScrollingInThisMotion) {
3684 scrollerWantsIt = onScrollTouch(ev);
3686 boolean horizontalSwipeWantsIt = false;
3687 if (!mIsBeingDragged
3688 && !mExpandingNotification
3689 && !mExpandedInThisMotion
3690 && !mOnlyScrollingInThisMotion
3691 && !mDisallowDismissInThisMotion) {
3692 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
3695 // Check if we need to clear any snooze leavebehinds
3696 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3697 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
3698 && guts.getGutsContent() instanceof NotificationSnooze) {
3699 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
3700 if ((ns.isExpanded() && isCancelOrUp)
3701 || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
3702 // If the leavebehind is expanded we clear it on the next up event, otherwise we
3703 // clear it on the next non-horizontal swipe or expand event.
3704 checkSnoozeLeavebehind();
3707 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3708 mCheckForLeavebehind = true;
3710 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
3713 @ShadeViewRefactor(RefactorComponent.INPUT)
3714 private void dispatchDownEventToScroller(MotionEvent ev) {
3715 MotionEvent downEvent = MotionEvent.obtain(ev);
3716 downEvent.setAction(MotionEvent.ACTION_DOWN);
3717 onScrollTouch(downEvent);
3718 downEvent.recycle();
3722 @ShadeViewRefactor(RefactorComponent.INPUT)
3723 public boolean onGenericMotionEvent(MotionEvent event) {
3724 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
3725 || mDisallowScrollingInThisMotion) {
3728 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3729 switch (event.getAction()) {
3730 case MotionEvent.ACTION_SCROLL: {
3731 if (!mIsBeingDragged) {
3732 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3734 final int delta = (int) (vscroll * getVerticalScrollFactor());
3735 if (ANCHOR_SCROLLING) {
3736 mScrollAnchorViewY -= delta;
3737 updateScrollAnchor();
3738 clampScrollPosition();
3739 updateOnScrollChange();
3741 final int range = getScrollRange();
3742 int oldScrollY = mOwnScrollY;
3743 int newScrollY = oldScrollY - delta;
3744 if (newScrollY < 0) {
3746 } else if (newScrollY > range) {
3749 if (newScrollY != oldScrollY) {
3750 setOwnScrollY(newScrollY);
3759 return super.onGenericMotionEvent(event);
3762 @ShadeViewRefactor(RefactorComponent.INPUT)
3763 private boolean onScrollTouch(MotionEvent ev) {
3764 if (!isScrollingEnabled()) {
3767 if (isInsideQsContainer(ev) && !mIsBeingDragged) {
3770 mForcedScroll = null;
3771 initVelocityTrackerIfNotExists();
3772 mVelocityTracker.addMovement(ev);
3774 final int action = ev.getAction();
3776 switch (action & MotionEvent.ACTION_MASK) {
3777 case MotionEvent.ACTION_DOWN: {
3778 if (getChildCount() == 0 || !isInContentBounds(ev)) {
3781 boolean isBeingDragged = !mScroller.isFinished();
3782 setIsBeingDragged(isBeingDragged);
3784 * If being flinged and user touches, stop the fling. isFinished
3785 * will be false if being flinged.
3787 if (!mScroller.isFinished()) {
3788 mScroller.forceFinished(true);
3791 // Remember where the motion event started
3792 mLastMotionY = (int) ev.getY();
3793 mDownX = (int) ev.getX();
3794 mActivePointerId = ev.getPointerId(0);
3797 case MotionEvent.ACTION_MOVE:
3798 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
3799 if (activePointerIndex == -1) {
3800 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
3804 final int y = (int) ev.getY(activePointerIndex);
3805 final int x = (int) ev.getX(activePointerIndex);
3806 int deltaY = mLastMotionY - y;
3807 final int xDiff = Math.abs(x - mDownX);
3808 final int yDiff = Math.abs(deltaY);
3809 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
3810 setIsBeingDragged(true);
3812 deltaY -= mTouchSlop;
3814 deltaY += mTouchSlop;
3817 if (mIsBeingDragged) {
3818 // Scroll to follow the motion event
3822 if (ANCHOR_SCROLLING) {
3823 range = 0; // unused in the methods it's being passed to
3825 range = getScrollRange();
3826 if (mExpandedInThisMotion) {
3827 range = Math.min(range, mMaxScrollAfterExpand);
3831 scrollAmount = overScrollDown(deltaY);
3833 scrollAmount = overScrollUp(deltaY, range);
3836 // Calling customOverScrollBy will call onCustomOverScrolled, which
3837 // sets the scrolling if applicable.
3838 if (scrollAmount != 0.0f) {
3839 // The scrolling motion could not be compensated with the
3840 // existing overScroll, we have to scroll the view
3841 customOverScrollBy((int) scrollAmount, mOwnScrollY,
3842 range, getHeight() / 2);
3843 // If we're scrolling, leavebehinds should be dismissed
3844 checkSnoozeLeavebehind();
3848 case MotionEvent.ACTION_UP:
3849 if (mIsBeingDragged) {
3850 final VelocityTracker velocityTracker = mVelocityTracker;
3851 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3852 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3854 if (shouldOverScrollFling(initialVelocity)) {
3855 onOverScrollFling(true, initialVelocity);
3857 if (getChildCount() > 0) {
3858 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
3859 float currentOverScrollTop = getCurrentOverScrollAmount(true);
3860 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
3861 fling(-initialVelocity);
3863 onOverScrollFling(false, initialVelocity);
3866 if (ANCHOR_SCROLLING) {
3869 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3870 getScrollRange())) {
3877 mActivePointerId = INVALID_POINTER;
3882 case MotionEvent.ACTION_CANCEL:
3883 if (mIsBeingDragged && getChildCount() > 0) {
3884 if (ANCHOR_SCROLLING) {
3887 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3888 getScrollRange())) {
3892 mActivePointerId = INVALID_POINTER;
3896 case MotionEvent.ACTION_POINTER_DOWN: {
3897 final int index = ev.getActionIndex();
3898 mLastMotionY = (int) ev.getY(index);
3899 mDownX = (int) ev.getX(index);
3900 mActivePointerId = ev.getPointerId(index);
3903 case MotionEvent.ACTION_POINTER_UP:
3904 onSecondaryPointerUp(ev);
3905 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
3906 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
3912 @ShadeViewRefactor(RefactorComponent.INPUT)
3913 protected boolean isInsideQsContainer(MotionEvent ev) {
3914 return ev.getY() < mQsContainer.getBottom();
3917 @ShadeViewRefactor(RefactorComponent.INPUT)
3918 private void onOverScrollFling(boolean open, int initialVelocity) {
3919 if (mOverscrollTopChangedListener != null) {
3920 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
3922 mDontReportNextOverScroll = true;
3923 setOverScrollAmount(0.0f, true, false);
3927 @ShadeViewRefactor(RefactorComponent.INPUT)
3928 private void onSecondaryPointerUp(MotionEvent ev) {
3929 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3930 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3931 final int pointerId = ev.getPointerId(pointerIndex);
3932 if (pointerId == mActivePointerId) {
3933 // This was our active pointer going up. Choose a new
3934 // active pointer and adjust accordingly.
3935 // TODO: Make this decision more intelligent.
3936 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3937 mLastMotionY = (int) ev.getY(newPointerIndex);
3938 mActivePointerId = ev.getPointerId(newPointerIndex);
3939 if (mVelocityTracker != null) {
3940 mVelocityTracker.clear();
3945 @ShadeViewRefactor(RefactorComponent.INPUT)
3946 private void endDrag() {
3947 setIsBeingDragged(false);
3949 recycleVelocityTracker();
3951 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
3952 setOverScrollAmount(0, true /* onTop */, true /* animate */);
3954 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
3955 setOverScrollAmount(0, false /* onTop */, true /* animate */);
3960 @ShadeViewRefactor(RefactorComponent.INPUT)
3961 public boolean onInterceptTouchEvent(MotionEvent ev) {
3963 handleEmptySpaceClick(ev);
3964 boolean expandWantsIt = false;
3965 boolean swipingInProgress = mSwipingInProgress;
3966 if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
3967 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
3969 boolean scrollWantsIt = false;
3970 if (!swipingInProgress && !mExpandingNotification) {
3971 scrollWantsIt = onInterceptTouchEventScroll(ev);
3973 boolean swipeWantsIt = false;
3974 if (!mIsBeingDragged
3975 && !mExpandingNotification
3976 && !mExpandedInThisMotion
3977 && !mOnlyScrollingInThisMotion
3978 && !mDisallowDismissInThisMotion) {
3979 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
3981 // Check if we need to clear any snooze leavebehinds
3982 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
3983 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3984 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
3985 !expandWantsIt && !scrollWantsIt) {
3986 mCheckForLeavebehind = false;
3987 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
3988 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
3989 false /* resetMenu */);
3991 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3992 mCheckForLeavebehind = true;
3994 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
3997 @ShadeViewRefactor(RefactorComponent.INPUT)
3998 private void handleEmptySpaceClick(MotionEvent ev) {
3999 switch (ev.getActionMasked()) {
4000 case MotionEvent.ACTION_MOVE:
4001 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
4002 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) {
4003 mTouchIsClick = false;
4006 case MotionEvent.ACTION_UP:
4007 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
4008 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
4009 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
4015 @ShadeViewRefactor(RefactorComponent.INPUT)
4016 private void initDownStates(MotionEvent ev) {
4017 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
4018 mExpandedInThisMotion = false;
4019 mOnlyScrollingInThisMotion = !mScroller.isFinished();
4020 mDisallowScrollingInThisMotion = false;
4021 mDisallowDismissInThisMotion = false;
4022 mTouchIsClick = true;
4023 mInitialTouchX = ev.getX();
4024 mInitialTouchY = ev.getY();
4029 @ShadeViewRefactor(RefactorComponent.INPUT)
4030 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4031 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4032 if (disallowIntercept) {
4037 @ShadeViewRefactor(RefactorComponent.INPUT)
4038 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
4039 if (!isScrollingEnabled()) {
4043 * This method JUST determines whether we want to intercept the motion.
4044 * If we return true, onMotionEvent will be called and we do the actual
4049 * Shortcut the most recurring case: the user is in the dragging
4050 * state and is moving their finger. We want to intercept this
4053 final int action = ev.getAction();
4054 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
4058 switch (action & MotionEvent.ACTION_MASK) {
4059 case MotionEvent.ACTION_MOVE: {
4061 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
4062 * whether the user has moved far enough from the original down touch.
4066 * Locally do absolute value. mLastMotionY is set to the y value
4067 * of the down event.
4069 final int activePointerId = mActivePointerId;
4070 if (activePointerId == INVALID_POINTER) {
4071 // If we don't have a valid id, the touch down wasn't on content.
4075 final int pointerIndex = ev.findPointerIndex(activePointerId);
4076 if (pointerIndex == -1) {
4077 Log.e(TAG, "Invalid pointerId=" + activePointerId
4078 + " in onInterceptTouchEvent");
4082 final int y = (int) ev.getY(pointerIndex);
4083 final int x = (int) ev.getX(pointerIndex);
4084 final int yDiff = Math.abs(y - mLastMotionY);
4085 final int xDiff = Math.abs(x - mDownX);
4086 if (yDiff > mTouchSlop && yDiff > xDiff) {
4087 setIsBeingDragged(true);
4090 initVelocityTrackerIfNotExists();
4091 mVelocityTracker.addMovement(ev);
4096 case MotionEvent.ACTION_DOWN: {
4097 final int y = (int) ev.getY();
4098 mScrolledToTopOnFirstDown = isScrolledToTop();
4099 if (getChildAtPosition(ev.getX(), y, false /* requireMinHeight */) == null) {
4100 setIsBeingDragged(false);
4101 recycleVelocityTracker();
4106 * Remember location of down touch.
4107 * ACTION_DOWN always refers to pointer index 0.
4110 mDownX = (int) ev.getX();
4111 mActivePointerId = ev.getPointerId(0);
4113 initOrResetVelocityTracker();
4114 mVelocityTracker.addMovement(ev);
4116 * If being flinged and user touches the screen, initiate drag;
4117 * otherwise don't. mScroller.isFinished should be false when
4120 boolean isBeingDragged = !mScroller.isFinished();
4121 setIsBeingDragged(isBeingDragged);
4125 case MotionEvent.ACTION_CANCEL:
4126 case MotionEvent.ACTION_UP:
4127 /* Release the drag */
4128 setIsBeingDragged(false);
4129 mActivePointerId = INVALID_POINTER;
4130 recycleVelocityTracker();
4131 if (ANCHOR_SCROLLING) {
4134 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
4139 case MotionEvent.ACTION_POINTER_UP:
4140 onSecondaryPointerUp(ev);
4145 * The only time we want to intercept motion events is if we are in the
4148 return mIsBeingDragged;
4152 * @return Whether the specified motion event is actually happening over the content.
4154 @ShadeViewRefactor(RefactorComponent.INPUT)
4155 private boolean isInContentBounds(MotionEvent event) {
4156 return isInContentBounds(event.getY());
4161 @ShadeViewRefactor(RefactorComponent.INPUT)
4162 void setIsBeingDragged(boolean isDragged) {
4163 mIsBeingDragged = isDragged;
4165 requestDisallowInterceptTouchEvent(true);
4167 resetExposedMenuView(true /* animate */, true /* force */);
4171 @ShadeViewRefactor(RefactorComponent.INPUT)
4172 public void requestDisallowLongPress() {
4176 @ShadeViewRefactor(RefactorComponent.INPUT)
4177 public void requestDisallowDismiss() {
4178 mDisallowDismissInThisMotion = true;
4181 @ShadeViewRefactor(RefactorComponent.INPUT)
4182 public void cancelLongPress() {
4183 mSwipeHelper.cancelLongPress();
4186 @ShadeViewRefactor(RefactorComponent.INPUT)
4187 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
4188 mOnEmptySpaceClickListener = listener;
4193 @ShadeViewRefactor(RefactorComponent.INPUT)
4194 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4195 if (super.performAccessibilityActionInternal(action, arguments)) {
4203 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
4205 case android.R.id.accessibilityActionScrollDown:
4208 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
4210 case android.R.id.accessibilityActionScrollUp:
4211 if (ANCHOR_SCROLLING) {
4214 final int viewportHeight =
4215 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
4216 - mShelf.getIntrinsicHeight();
4217 final int targetScrollY = Math.max(0,
4218 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
4219 if (targetScrollY != mOwnScrollY) {
4220 mScroller.startScroll(mScrollX, mOwnScrollY, 0,
4221 targetScrollY - mOwnScrollY);
4231 @ShadeViewRefactor(RefactorComponent.INPUT)
4232 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4233 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
4234 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
4235 View translatingParentView = mSwipeHelper.getTranslatingParentView();
4237 if (guts != null && !guts.getGutsContent().isLeavebehind()) {
4238 // Only close visible guts if they're not a leavebehind.
4240 } else if (menuRow != null && menuRow.isMenuVisible()
4241 && translatingParentView != null) {
4243 view = translatingParentView;
4245 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
4246 // Touch was outside visible guts / menu notification, close what's visible
4247 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
4248 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
4249 false /* resetMenu */);
4250 resetExposedMenuView(true /* animate */, true /* force */);
4254 @ShadeViewRefactor(RefactorComponent.INPUT)
4255 private void setSwipingInProgress(boolean swiping) {
4256 mSwipingInProgress = swiping;
4258 requestDisallowInterceptTouchEvent(true);
4263 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4264 public void onWindowFocusChanged(boolean hasWindowFocus) {
4265 super.onWindowFocusChanged(hasWindowFocus);
4266 if (!hasWindowFocus) {
4272 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4273 public void clearChildFocus(View child) {
4274 super.clearChildFocus(child);
4275 if (mForcedScroll == child) {
4276 mForcedScroll = null;
4281 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4282 public boolean isScrolledToTop() {
4283 if (ANCHOR_SCROLLING) {
4284 updateScrollAnchor();
4285 // TODO: once we're recycling this will need to check the adapter position of the child
4286 return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
4288 return mOwnScrollY == 0;
4293 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4294 public boolean isScrolledToBottom() {
4295 if (ANCHOR_SCROLLING) {
4296 return getMaxPositiveScrollAmount() <= 0;
4298 return mOwnScrollY >= getScrollRange();
4303 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4304 public View getHostView() {
4308 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4309 public int getEmptyBottomMargin() {
4310 return Math.max(mMaxLayoutHeight - mContentHeight, 0);
4313 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4314 public void checkSnoozeLeavebehind() {
4315 if (mCheckForLeavebehind) {
4316 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
4317 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4318 false /* resetMenu */);
4319 mCheckForLeavebehind = false;
4323 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4324 public void resetCheckSnoozeLeavebehind() {
4325 mCheckForLeavebehind = true;
4328 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4329 public void onExpansionStarted() {
4330 mIsExpansionChanging = true;
4331 mAmbientState.setExpansionChanging(true);
4332 checkSnoozeLeavebehind();
4335 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4336 public void onExpansionStopped() {
4337 mIsExpansionChanging = false;
4338 resetCheckSnoozeLeavebehind();
4339 mAmbientState.setExpansionChanging(false);
4341 resetScrollPosition();
4342 mStatusBar.resetUserExpandedStates();
4343 clearTemporaryViews();
4344 clearUserLockedViews();
4345 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
4346 if (draggedViews.size() > 0) {
4347 draggedViews.clear();
4348 updateContinuousShadowDrawing();
4353 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4354 private void clearUserLockedViews() {
4355 for (int i = 0; i < getChildCount(); i++) {
4356 ExpandableView child = (ExpandableView) getChildAt(i);
4357 if (child instanceof ExpandableNotificationRow) {
4358 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4359 row.setUserLocked(false);
4364 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4365 private void clearTemporaryViews() {
4366 // lets make sure nothing is transient anymore
4367 clearTemporaryViewsInGroup(this);
4368 for (int i = 0; i < getChildCount(); i++) {
4369 ExpandableView child = (ExpandableView) getChildAt(i);
4370 if (child instanceof ExpandableNotificationRow) {
4371 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4372 clearTemporaryViewsInGroup(row.getChildrenContainer());
4377 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4378 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
4379 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
4380 viewGroup.removeTransientView(viewGroup.getTransientView(0));
4384 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4385 public void onPanelTrackingStarted() {
4386 mPanelTracking = true;
4387 mAmbientState.setPanelTracking(true);
4388 resetExposedMenuView(true /* animate */, true /* force */);
4391 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4392 public void onPanelTrackingStopped() {
4393 mPanelTracking = false;
4394 mAmbientState.setPanelTracking(false);
4397 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4398 public void resetScrollPosition() {
4399 mScroller.abortAnimation();
4400 if (ANCHOR_SCROLLING) {
4401 // TODO: once we're recycling this will need to modify the adapter position instead
4402 mScrollAnchorView = getFirstChildNotGone();
4403 mScrollAnchorViewY = 0;
4404 updateOnScrollChange();
4410 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4411 private void setIsExpanded(boolean isExpanded) {
4412 boolean changed = isExpanded != mIsExpanded;
4413 mIsExpanded = isExpanded;
4414 mStackScrollAlgorithm.setIsExpanded(isExpanded);
4415 mAmbientState.setShadeExpanded(isExpanded);
4416 mStateAnimator.setShadeExpanded(isExpanded);
4417 mSwipeHelper.setIsExpanded(isExpanded);
4419 mWillExpand = false;
4421 mGroupManager.collapseAllGroups();
4422 mExpandHelper.cancelImmediately();
4424 updateNotificationAnimationStates();
4425 updateChronometers();
4426 requestChildrenUpdate();
4430 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4431 private void updateChronometers() {
4432 int childCount = getChildCount();
4433 for (int i = 0; i < childCount; i++) {
4434 updateChronometerForChild(getChildAt(i));
4438 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4439 private void updateChronometerForChild(View child) {
4440 if (child instanceof ExpandableNotificationRow) {
4441 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4442 row.setChronometerRunning(mIsExpanded);
4447 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
4448 updateContentHeight();
4449 updateScrollPositionOnExpandInBottom(view);
4450 clampScrollPosition();
4451 notifyHeightChangeListener(view, needsAnimation);
4452 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
4453 ? (ExpandableNotificationRow) view
4455 NotificationSection firstSection = getFirstVisibleSection();
4456 ActivatableNotificationView firstVisibleChild =
4457 firstSection == null ? null : firstSection.getFirstVisibleChild();
4459 if (row == firstVisibleChild
4460 || row.getNotificationParent() == firstVisibleChild) {
4461 updateAlgorithmLayoutMinHeight();
4464 if (needsAnimation) {
4465 requestAnimationOnViewResize(row);
4467 requestChildrenUpdate();
4471 public void onReset(ExpandableView view) {
4472 updateAnimationState(view);
4473 updateChronometerForChild(view);
4476 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4477 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
4478 if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
4479 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
4480 // TODO: once we're recycling this will need to check the adapter position of the child
4481 if (row.isUserLocked() && row != getFirstChildNotGone()) {
4482 if (row.isSummaryWithChildren()) {
4485 // We are actually expanding this view
4486 float endPosition = row.getTranslationY() + row.getActualHeight();
4487 if (row.isChildInGroup()) {
4488 endPosition += row.getNotificationParent().getTranslationY();
4490 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
4491 NotificationSection lastSection = getLastVisibleSection();
4492 ActivatableNotificationView lastVisibleChild =
4493 lastSection == null ? null : lastSection.getLastVisibleChild();
4494 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) {
4495 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
4497 if (endPosition > layoutEnd) {
4498 if (ANCHOR_SCROLLING) {
4499 mScrollAnchorViewY -= (endPosition - layoutEnd);
4500 updateScrollAnchor();
4501 updateOnScrollChange();
4503 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
4505 mDisallowScrollingInThisMotion = true;
4511 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4512 public void setOnHeightChangedListener(
4513 ExpandableView.OnHeightChangedListener onHeightChangedListener) {
4514 this.mOnHeightChangedListener = onHeightChangedListener;
4517 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4518 public void onChildAnimationFinished() {
4519 setAnimationRunning(false);
4520 requestChildrenUpdate();
4521 runAnimationFinishedRunnables();
4523 clearHeadsUpDisappearRunning();
4526 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4527 private void clearHeadsUpDisappearRunning() {
4528 for (int i = 0; i < getChildCount(); i++) {
4529 View view = getChildAt(i);
4530 if (view instanceof ExpandableNotificationRow) {
4531 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
4532 row.setHeadsUpAnimatingAway(false);
4533 if (row.isSummaryWithChildren()) {
4534 for (ExpandableNotificationRow child : row.getNotificationChildren()) {
4535 child.setHeadsUpAnimatingAway(false);
4542 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4543 private void clearTransient() {
4544 for (ExpandableView view : mClearTransientViewsWhenFinished) {
4545 StackStateAnimator.removeTransientView(view);
4547 mClearTransientViewsWhenFinished.clear();
4550 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4551 private void runAnimationFinishedRunnables() {
4552 for (Runnable runnable : mAnimationFinishedRunnables) {
4555 mAnimationFinishedRunnables.clear();
4559 * See {@link AmbientState#setDimmed}.
4561 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4562 public void setDimmed(boolean dimmed, boolean animate) {
4563 dimmed &= onKeyguard();
4564 mAmbientState.setDimmed(dimmed);
4565 if (animate && mAnimationsEnabled) {
4566 mDimmedNeedsAnimation = true;
4567 mNeedsAnimation = true;
4568 animateDimmed(dimmed);
4570 setDimAmount(dimmed ? 1.0f : 0.0f);
4572 requestChildrenUpdate();
4576 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4577 boolean isDimmed() {
4578 return mAmbientState.isDimmed();
4581 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4582 private void setDimAmount(float dimAmount) {
4583 mDimAmount = dimAmount;
4584 updateBackgroundDimming();
4587 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4588 private void animateDimmed(boolean dimmed) {
4589 if (mDimAnimator != null) {
4590 mDimAnimator.cancel();
4592 float target = dimmed ? 1.0f : 0.0f;
4593 if (target == mDimAmount) {
4596 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
4597 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
4598 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
4599 mDimAnimator.addListener(mDimEndListener);
4600 mDimAnimator.addUpdateListener(mDimUpdateListener);
4601 mDimAnimator.start();
4604 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4605 private void setHideSensitive(boolean hideSensitive, boolean animate) {
4606 if (hideSensitive != mAmbientState.isHideSensitive()) {
4607 int childCount = getChildCount();
4608 for (int i = 0; i < childCount; i++) {
4609 ExpandableView v = (ExpandableView) getChildAt(i);
4610 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
4612 mAmbientState.setHideSensitive(hideSensitive);
4613 if (animate && mAnimationsEnabled) {
4614 mHideSensitiveNeedsAnimation = true;
4615 mNeedsAnimation = true;
4617 updateContentHeight();
4618 requestChildrenUpdate();
4623 * See {@link AmbientState#setActivatedChild}.
4625 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4626 public void setActivatedChild(ActivatableNotificationView activatedChild) {
4627 mAmbientState.setActivatedChild(activatedChild);
4628 if (mAnimationsEnabled) {
4629 mActivateNeedsAnimation = true;
4630 mNeedsAnimation = true;
4632 requestChildrenUpdate();
4635 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4636 public ActivatableNotificationView getActivatedChild() {
4637 return mAmbientState.getActivatedChild();
4640 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4641 private void applyCurrentState() {
4642 int numChildren = getChildCount();
4643 for (int i = 0; i < numChildren; i++) {
4644 ExpandableView child = (ExpandableView) getChildAt(i);
4645 child.applyViewState();
4648 if (mListener != null) {
4649 mListener.onChildLocationsChanged();
4651 runAnimationFinishedRunnables();
4652 setAnimationRunning(false);
4654 updateViewShadows();
4655 updateClippingToTopRoundedCorner();
4658 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4659 private void updateViewShadows() {
4660 // we need to work around an issue where the shadow would not cast between siblings when
4661 // their z difference is between 0 and 0.1
4663 // Lefts first sort by Z difference
4664 for (int i = 0; i < getChildCount(); i++) {
4665 ExpandableView child = (ExpandableView) getChildAt(i);
4666 if (child.getVisibility() != GONE) {
4667 mTmpSortedChildren.add(child);
4670 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
4672 // Now lets update the shadow for the views
4673 ExpandableView previous = null;
4674 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
4675 ExpandableView expandableView = mTmpSortedChildren.get(i);
4676 float translationZ = expandableView.getTranslationZ();
4677 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
4678 float diff = otherZ - translationZ;
4679 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
4680 // There is no fake shadow to be drawn
4681 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
4683 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
4684 expandableView.getTranslationY() - previous.getExtraBottomPadding();
4685 expandableView.setFakeShadowIntensity(
4686 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
4687 previous.getOutlineAlpha(), (int) yLocation,
4688 previous.getOutlineTranslation());
4690 previous = expandableView;
4693 mTmpSortedChildren.clear();
4697 * Update colors of "dismiss" and "empty shade" views.
4699 * @param lightTheme True if light theme should be used.
4701 @ShadeViewRefactor(RefactorComponent.DECORATOR)
4702 public void updateDecorViews(boolean lightTheme) {
4703 if (lightTheme == mUsingLightTheme) {
4706 mUsingLightTheme = lightTheme;
4707 Context context = new ContextThemeWrapper(mContext,
4708 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
4709 final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
4710 mFooterView.setTextColor(textColor);
4711 mEmptyShadeView.setTextColor(textColor);
4714 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4715 public void goToFullShade(long delay) {
4716 mGoToFullShadeNeedsAnimation = true;
4717 mGoToFullShadeDelay = delay;
4718 mNeedsAnimation = true;
4719 requestChildrenUpdate();
4722 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4723 public void cancelExpandHelper() {
4724 mExpandHelper.cancel();
4727 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4728 public void setIntrinsicPadding(int intrinsicPadding) {
4729 mIntrinsicPadding = intrinsicPadding;
4730 mAmbientState.setIntrinsicPadding(intrinsicPadding);
4733 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4734 public int getIntrinsicPadding() {
4735 return mIntrinsicPadding;
4739 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4740 public boolean shouldDelayChildPressedState() {
4745 * See {@link AmbientState#setDozing}.
4747 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4748 public void setDozing(boolean dozing, boolean animate,
4749 @Nullable PointF touchWakeUpScreenLocation) {
4750 if (mAmbientState.isDozing() == dozing) {
4753 mAmbientState.setDozing(dozing);
4754 requestChildrenUpdate();
4755 notifyHeightChangeListener(mShelf);
4759 * Sets the current hide amount.
4761 * @param linearHideAmount The hide amount that follows linear interpoloation in the
4763 * i.e. animates from 0 to 1 or vice-versa in a linear manner.
4764 * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the
4767 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4768 public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
4769 mLinearHideAmount = linearHideAmount;
4770 mInterpolatedHideAmount = interpolatedHideAmount;
4771 boolean wasFullyHidden = mAmbientState.isFullyHidden();
4772 boolean wasHiddenAtAll = mAmbientState.isHiddenAtAll();
4773 mAmbientState.setHideAmount(interpolatedHideAmount);
4774 boolean nowFullyHidden = mAmbientState.isFullyHidden();
4775 boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
4776 if (nowFullyHidden != wasFullyHidden) {
4779 if (!wasHiddenAtAll && nowHiddenAtAll) {
4780 resetExposedMenuView(true /* animate */, true /* animate */);
4782 if (nowFullyHidden != wasFullyHidden || wasHiddenAtAll != nowHiddenAtAll) {
4783 invalidateOutline();
4785 updateAlgorithmHeightAndPadding();
4786 updateBackgroundDimming();
4787 requestChildrenUpdate();
4788 updateOwnTranslationZ();
4791 private void updateOwnTranslationZ() {
4792 // Since we are clipping to the outline we need to make sure that the shadows aren't
4793 // clipped when pulsing
4794 float ownTranslationZ = 0;
4795 if (mKeyguardBypassController.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
4796 ExpandableView firstChildNotGone = getFirstChildNotGone();
4797 if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
4798 ownTranslationZ = firstChildNotGone.getTranslationZ();
4801 setTranslationZ(ownTranslationZ);
4804 private void updateVisibility() {
4805 boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard();
4806 setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
4809 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4810 public void notifyHideAnimationStart(boolean hide) {
4811 // We only swap the scaling factor if we're fully hidden or fully awake to avoid
4812 // interpolation issues when playing with the power button.
4813 if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) {
4814 mBackgroundXFactor = hide ? 1.8f : 1.5f;
4815 mHideXInterpolator = hide
4816 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE
4817 : Interpolators.FAST_OUT_SLOW_IN;
4821 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4822 private int getNotGoneIndex(View child) {
4823 int count = getChildCount();
4824 int notGoneIndex = 0;
4825 for (int i = 0; i < count; i++) {
4826 View v = getChildAt(i);
4828 return notGoneIndex;
4830 if (v.getVisibility() != View.GONE) {
4837 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4838 public void setFooterView(@NonNull FooterView footerView) {
4840 if (mFooterView != null) {
4841 index = indexOfChild(mFooterView);
4842 removeView(mFooterView);
4844 mFooterView = footerView;
4845 addView(mFooterView, index);
4848 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4849 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
4851 if (mEmptyShadeView != null) {
4852 index = indexOfChild(mEmptyShadeView);
4853 removeView(mEmptyShadeView);
4855 mEmptyShadeView = emptyShadeView;
4856 addView(mEmptyShadeView, index);
4859 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4860 public void updateEmptyShadeView(boolean visible) {
4861 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
4863 int oldTextRes = mEmptyShadeView.getTextResource();
4864 int newTextRes = mStatusBar.areNotificationsHidden()
4865 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
4866 if (oldTextRes != newTextRes) {
4867 mEmptyShadeView.setText(newTextRes);
4871 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4872 public void updateFooterView(boolean visible, boolean showDismissView) {
4873 if (mFooterView == null) {
4876 boolean animate = mIsExpanded && mAnimationsEnabled;
4877 mFooterView.setVisible(visible, animate);
4878 mFooterView.setSecondaryVisible(showDismissView, animate);
4881 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4882 public void setDismissAllInProgress(boolean dismissAllInProgress) {
4883 mDismissAllInProgress = dismissAllInProgress;
4884 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
4885 handleDismissAllClipping();
4888 @ShadeViewRefactor(RefactorComponent.ADAPTER)
4889 private void handleDismissAllClipping() {
4890 final int count = getChildCount();
4891 boolean previousChildWillBeDismissed = false;
4892 for (int i = 0; i < count; i++) {
4893 ExpandableView child = (ExpandableView) getChildAt(i);
4894 if (child.getVisibility() == GONE) {
4897 if (mDismissAllInProgress && previousChildWillBeDismissed) {
4898 child.setMinClipTopAmount(child.getClipTopAmount());
4900 child.setMinClipTopAmount(0);
4902 previousChildWillBeDismissed = StackScrollAlgorithm.canChildBeDismissed(child);
4906 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4907 public boolean isFooterViewNotGone() {
4908 return mFooterView != null
4909 && mFooterView.getVisibility() != View.GONE
4910 && !mFooterView.willBeGone();
4913 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4914 public boolean isFooterViewContentVisible() {
4915 return mFooterView != null && mFooterView.isContentVisible();
4918 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4919 public int getFooterViewHeight() {
4920 return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements;
4923 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4924 public int getEmptyShadeViewHeight() {
4925 return mEmptyShadeView.getHeight();
4928 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4929 public float getBottomMostNotificationBottom() {
4930 final int count = getChildCount();
4932 for (int childIdx = 0; childIdx < count; childIdx++) {
4933 ExpandableView child = (ExpandableView) getChildAt(childIdx);
4934 if (child.getVisibility() == GONE) {
4937 float bottom = child.getTranslationY() + child.getActualHeight()
4938 - child.getClipBottomAmount();
4943 return max + getStackTranslation();
4946 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4947 public void setStatusBar(StatusBar statusBar) {
4948 this.mStatusBar = statusBar;
4951 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
4952 public void setGroupManager(NotificationGroupManager groupManager) {
4953 this.mGroupManager = groupManager;
4954 mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
4957 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
4958 private void requestAnimateEverything() {
4959 if (mIsExpanded && mAnimationsEnabled) {
4960 mEverythingNeedsAnimation = true;
4961 mNeedsAnimation = true;
4962 requestChildrenUpdate();
4966 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
4967 public boolean isBelowLastNotification(float touchX, float touchY) {
4968 int childCount = getChildCount();
4969 for (int i = childCount - 1; i >= 0; i--) {
4970 ExpandableView child = (ExpandableView) getChildAt(i);
4971 if (child.getVisibility() != View.GONE) {
4972 float childTop = child.getY();
4973 if (childTop > touchY) {
4974 // we are above a notification entirely let's abort
4977 boolean belowChild = touchY > childTop + child.getActualHeight()
4978 - child.getClipBottomAmount();
4979 if (child == mFooterView) {
4980 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
4981 touchY - childTop)) {
4982 // We clicked on the dismiss button
4985 } else if (child == mEmptyShadeView) {
4986 // We arrived at the empty shade view, for which we accept all clicks
4988 } else if (!belowChild) {
4989 // We are on a child
4994 return touchY > mTopPadding + mStackTranslation;
4999 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5000 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
5001 super.onInitializeAccessibilityEventInternal(event);
5002 event.setScrollable(mScrollable);
5003 event.setScrollX(mScrollX);
5004 event.setMaxScrollX(mScrollX);
5005 if (ANCHOR_SCROLLING) {
5008 event.setScrollY(mOwnScrollY);
5009 event.setMaxScrollY(getScrollRange());
5014 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5015 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
5016 super.onInitializeAccessibilityNodeInfoInternal(info);
5018 info.setScrollable(true);
5019 if (mBackwardScrollable) {
5021 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
5022 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
5024 if (mForwardScrollable) {
5025 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
5026 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
5029 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
5030 info.setClassName(ScrollView.class.getName());
5033 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
5034 public void generateChildOrderChangedEvent() {
5035 if (mIsExpanded && mAnimationsEnabled) {
5036 mGenerateChildOrderChangedEvent = true;
5037 mNeedsAnimation = true;
5038 requestChildrenUpdate();
5043 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5044 public int getContainerChildCount() {
5045 return getChildCount();
5049 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5050 public View getContainerChildAt(int i) {
5051 return getChildAt(i);
5055 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5056 public void removeContainerView(View v) {
5057 Assert.isMainThread();
5062 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5063 public void addContainerView(View v) {
5064 Assert.isMainThread();
5068 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5069 public void runAfterAnimationFinished(Runnable runnable) {
5070 mAnimationFinishedRunnables.add(runnable);
5073 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
5074 ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
5075 generateHeadsUpAnimation(row, isHeadsUp);
5078 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5079 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
5080 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
5081 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
5082 mNeedsAnimation = true;
5083 if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
5084 row.setHeadsUpAnimatingAway(true);
5086 requestChildrenUpdate();
5091 * Set the boundary for the bottom heads up position. The heads up will always be above this
5094 * @param height the height of the screen
5095 * @param bottomBarHeight the height of the bar on the bottom
5097 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5098 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
5099 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
5100 mStateAnimator.setHeadsUpAppearHeightBottom(height);
5101 requestChildrenUpdate();
5105 public void setWillExpand(boolean willExpand) {
5106 mWillExpand = willExpand;
5109 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5110 public void setTrackingHeadsUp(ExpandableNotificationRow row) {
5111 mTrackingHeadsUp = row != null;
5112 mRoundnessManager.setTrackingHeadsUp(row);
5115 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5116 public void setScrimController(ScrimController scrimController) {
5117 mScrimController = scrimController;
5118 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
5121 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5122 public void forceNoOverlappingRendering(boolean force) {
5123 mForceNoOverlappingRendering = force;
5127 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5128 public boolean hasOverlappingRendering() {
5129 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
5132 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5133 public void setAnimationRunning(boolean animationRunning) {
5134 if (animationRunning != mAnimationRunning) {
5135 if (animationRunning) {
5136 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
5138 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
5140 mAnimationRunning = animationRunning;
5141 updateContinuousShadowDrawing();
5145 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5146 public boolean isExpanded() {
5150 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5151 public void setPulsing(boolean pulsing, boolean animated) {
5152 if (!mPulsing && !pulsing) {
5156 mAmbientState.setPulsing(pulsing);
5157 mSwipeHelper.setPulsing(pulsing);
5158 updateNotificationAnimationStates();
5159 updateAlgorithmHeightAndPadding();
5160 updateContentHeight();
5161 requestChildrenUpdate();
5162 notifyHeightChangeListener(null, animated);
5165 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5166 public void setQsExpanded(boolean qsExpanded) {
5167 mQsExpanded = qsExpanded;
5168 updateAlgorithmLayoutMinHeight();
5169 updateScrollability();
5172 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5173 public void setQsExpansionFraction(float qsExpansionFraction) {
5174 mQsExpansionFraction = qsExpansionFraction;
5177 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
5178 private void setOwnScrollY(int ownScrollY) {
5179 assert !ANCHOR_SCROLLING;
5180 if (ownScrollY != mOwnScrollY) {
5181 // We still want to call the normal scrolled changed for accessibility reasons
5182 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
5183 mOwnScrollY = ownScrollY;
5184 updateOnScrollChange();
5188 private void updateOnScrollChange() {
5189 updateForwardAndBackwardScrollability();
5190 requestChildrenUpdate();
5193 private void updateScrollAnchor() {
5194 int anchorIndex = indexOfChild(mScrollAnchorView);
5195 // If the anchor view has been scrolled off the top, move to the next view.
5196 while (mScrollAnchorViewY < 0) {
5197 View nextAnchor = null;
5198 for (int i = anchorIndex + 1; i < getChildCount(); i++) {
5199 View child = getChildAt(i);
5200 if (child.getVisibility() != View.GONE
5201 && child instanceof ExpandableNotificationRow) {
5207 if (nextAnchor == null) {
5210 mScrollAnchorViewY +=
5211 (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
5212 mScrollAnchorView = nextAnchor;
5214 // If the view above the anchor view is fully visible, make it the anchor view.
5215 while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
5216 View prevAnchor = null;
5217 for (int i = anchorIndex - 1; i >= 0; i--) {
5218 View child = getChildAt(i);
5219 if (child.getVisibility() != View.GONE
5220 && child instanceof ExpandableNotificationRow) {
5226 if (prevAnchor == null) {
5229 float distanceToPreviousAnchor =
5230 mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
5231 if (distanceToPreviousAnchor < mScrollAnchorViewY) {
5232 mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
5233 mScrollAnchorView = prevAnchor;
5238 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5239 public void setShelf(NotificationShelf shelf) {
5241 if (mShelf != null) {
5242 index = indexOfChild(mShelf);
5246 addView(mShelf, index);
5247 mAmbientState.setShelf(shelf);
5248 mStateAnimator.setShelf(shelf);
5249 shelf.bind(mAmbientState, this);
5250 if (ANCHOR_SCROLLING) {
5251 mScrollAnchorView = mShelf;
5255 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5256 public NotificationShelf getNotificationShelf() {
5260 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5261 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
5262 if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
5263 mMaxDisplayedNotifications = maxDisplayedNotifications;
5264 updateContentHeight();
5265 notifyHeightChangeListener(mShelf);
5269 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5270 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
5271 mShouldShowShelfOnly = shouldShowShelfOnly;
5272 updateAlgorithmLayoutMinHeight();
5275 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
5276 public int getMinExpansionHeight() {
5277 return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
5280 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5281 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
5282 mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
5286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5287 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
5288 mHeadsUpAnimatingAway = headsUpAnimatingAway;
5292 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5294 protected void setStatusBarState(int statusBarState) {
5295 mStatusBarState = statusBarState;
5296 mAmbientState.setStatusBarState(statusBarState);
5299 private void onStatePostChange() {
5300 boolean onKeyguard = onKeyguard();
5301 boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode();
5303 if (mHeadsUpAppearanceController != null) {
5304 mHeadsUpAppearanceController.onStateChanged();
5307 SysuiStatusBarStateController state = (SysuiStatusBarStateController)
5308 Dependency.get(StatusBarStateController.class);
5309 setHideSensitive(publicMode, state.goingToFullShade() /* animate */);
5310 setDimmed(onKeyguard, state.fromShadeLocked() /* animate */);
5311 setExpandingEnabled(!onKeyguard);
5312 ActivatableNotificationView activatedChild = getActivatedChild();
5313 setActivatedChild(null);
5314 if (activatedChild != null) {
5315 activatedChild.makeInactive(false /* animate */);
5318 requestChildrenUpdate();
5319 onUpdateRowStates();
5321 mEntryManager.updateNotifications();
5325 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5326 public void setExpandingVelocity(float expandingVelocity) {
5327 mAmbientState.setExpandingVelocity(expandingVelocity);
5330 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
5331 public float getOpeningHeight() {
5332 if (mEmptyShadeView.getVisibility() == GONE) {
5333 return getMinExpansionHeight();
5335 return getAppearEndPosition();
5339 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5340 public void setIsFullWidth(boolean isFullWidth) {
5341 mAmbientState.setPanelFullWidth(isFullWidth);
5344 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5345 public void setUnlockHintRunning(boolean running) {
5346 mAmbientState.setUnlockHintRunning(running);
5349 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5350 public void setQsCustomizerShowing(boolean isShowing) {
5351 mAmbientState.setQsCustomizerShowing(isShowing);
5352 requestChildrenUpdate();
5355 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5356 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
5357 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
5360 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5361 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
5362 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
5363 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
5364 + " qsExpandFraction=%f]",
5365 this.getClass().getSimpleName(),
5366 mPulsing ? "T" : "f",
5367 mAmbientState.isQsCustomizerShowing() ? "T" : "f",
5368 getVisibility() == View.VISIBLE ? "visible"
5369 : getVisibility() == View.GONE ? "gone"
5372 mAmbientState.getScrollY(),
5374 mShouldShowShelfOnly ? "T" : "f",
5375 mQsExpansionFraction));
5376 int childCount = getChildCount();
5377 pw.println(" Number of children: " + childCount);
5380 for (int i = 0; i < childCount; i++) {
5381 ExpandableView child = (ExpandableView) getChildAt(i);
5382 child.dump(fd, pw, args);
5383 if (!(child instanceof ExpandableNotificationRow)) {
5384 pw.println(" " + child.getClass().getSimpleName());
5385 // Notifications dump it's viewstate as part of their dump to support children
5386 ExpandableViewState viewState = child.getViewState();
5387 if (viewState == null) {
5388 pw.println(" no viewState!!!");
5391 viewState.dump(fd, pw, args);
5397 int transientViewCount = getTransientViewCount();
5398 pw.println(" Transient Views: " + transientViewCount);
5399 for (int i = 0; i < transientViewCount; i++) {
5400 ExpandableView child = (ExpandableView) getTransientView(i);
5401 child.dump(fd, pw, args);
5403 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
5404 int draggedCount = draggedViews.size();
5405 pw.println(" Dragged Views: " + draggedCount);
5406 for (int i = 0; i < draggedCount; i++) {
5407 ExpandableView child = (ExpandableView) draggedViews.get(i);
5408 child.dump(fd, pw, args);
5412 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5413 public boolean isFullyHidden() {
5414 return mAmbientState.isFullyHidden();
5418 * Add a listener whenever the expanded height changes. The first value passed as an
5419 * argument is the expanded height and the second one is the appearFraction.
5421 * @param listener the listener to notify.
5423 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5424 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
5425 mExpandedHeightListeners.add(listener);
5429 * Stop a listener from listening to the expandedHeight.
5431 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5432 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
5433 mExpandedHeightListeners.remove(listener);
5436 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5437 public void setHeadsUpAppearanceController(
5438 HeadsUpAppearanceController headsUpAppearanceController) {
5439 mHeadsUpAppearanceController = headsUpAppearanceController;
5442 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5443 public void setIconAreaController(NotificationIconAreaController controller) {
5444 mIconAreaController = controller;
5447 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5448 public void manageNotifications(View v) {
5449 Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
5450 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
5453 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5454 private void clearNotifications(
5455 @SelectedRows int selection,
5456 boolean closeShade) {
5457 // animate-swipe all dismissable notifications, then animate the shade closed
5458 int numChildren = getChildCount();
5460 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
5461 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
5462 for (int i = 0; i < numChildren; i++) {
5463 final View child = getChildAt(i);
5464 if (child instanceof ExpandableNotificationRow) {
5465 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
5466 boolean parentVisible = false;
5467 boolean hasClipBounds = child.getClipBounds(mTmpRect);
5468 if (includeChildInDismissAll(row, selection)) {
5469 viewsToRemove.add(row);
5470 if (child.getVisibility() == View.VISIBLE
5471 && (!hasClipBounds || mTmpRect.height() > 0)) {
5472 viewsToHide.add(child);
5473 parentVisible = true;
5475 } else if (child.getVisibility() == View.VISIBLE
5476 && (!hasClipBounds || mTmpRect.height() > 0)) {
5477 parentVisible = true;
5479 List<ExpandableNotificationRow> children = row.getNotificationChildren();
5480 if (children != null) {
5481 for (ExpandableNotificationRow childRow : children) {
5482 if (includeChildInDismissAll(row, selection)) {
5483 viewsToRemove.add(childRow);
5484 if (parentVisible && row.areChildrenExpanded()) {
5485 hasClipBounds = childRow.getClipBounds(mTmpRect);
5486 if (childRow.getVisibility() == View.VISIBLE
5487 && (!hasClipBounds || mTmpRect.height() > 0)) {
5488 viewsToHide.add(childRow);
5497 if (viewsToRemove.isEmpty()) {
5499 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5504 performDismissAllAnimations(viewsToHide, closeShade, () -> {
5505 for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
5506 if (StackScrollAlgorithm.canChildBeDismissed(rowToRemove)) {
5507 if (selection == ROWS_ALL) {
5508 // TODO: This is a listener method; we shouldn't be calling it. Can we just
5509 // call performRemoveNotification as below?
5510 mEntryManager.removeNotification(
5511 rowToRemove.getEntry().key,
5513 NotificationListenerService.REASON_CANCEL_ALL);
5515 mEntryManager.performRemoveNotification(
5516 rowToRemove.getEntry().notification,
5517 NotificationListenerService.REASON_CANCEL_ALL);
5520 rowToRemove.resetTranslation();
5523 if (selection == ROWS_ALL) {
5525 mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
5526 } catch (Exception ex) {
5532 private boolean includeChildInDismissAll(
5533 ExpandableNotificationRow row,
5534 @SelectedRows int selection) {
5535 return StackScrollAlgorithm.canChildBeDismissed(row) && matchesSelection(row, selection);
5539 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
5540 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
5543 * @param hideAnimatedList List of rows to animated away. Should only be views that are
5544 * currently visible, or else the stagger will look funky.
5545 * @param closeShade Whether to close the shade after the stagger animation completes.
5546 * @param onAnimationComplete Called after the entire animation completes (including the shade
5547 * closing if appropriate). The rows must be dismissed for real here.
5549 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5550 private void performDismissAllAnimations(
5551 final ArrayList<View> hideAnimatedList,
5552 final boolean closeShade,
5553 final Runnable onAnimationComplete) {
5555 final Runnable onSlideAwayAnimationComplete = () -> {
5557 mShadeController.addPostCollapseAction(() -> {
5558 setDismissAllInProgress(false);
5559 onAnimationComplete.run();
5561 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5563 setDismissAllInProgress(false);
5564 onAnimationComplete.run();
5568 if (hideAnimatedList.isEmpty()) {
5569 onSlideAwayAnimationComplete.run();
5573 // let's disable our normal animations
5574 setDismissAllInProgress(true);
5576 // Decrease the delay for every row we animate to give the sense of
5577 // accelerating the swipes
5578 int rowDelayDecrement = 10;
5579 int currentDelay = 140;
5580 int totalDelay = 180;
5581 int numItems = hideAnimatedList.size();
5582 for (int i = numItems - 1; i >= 0; i--) {
5583 View view = hideAnimatedList.get(i);
5584 Runnable endRunnable = null;
5586 endRunnable = onSlideAwayAnimationComplete;
5588 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
5589 currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
5590 totalDelay += currentDelay;
5595 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5596 protected void inflateFooterView() {
5597 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
5598 R.layout.status_bar_notification_footer, this, false);
5599 footerView.setDismissButtonClickListener(v -> {
5600 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
5601 clearNotifications(ROWS_ALL, true /* closeShade */);
5603 footerView.setManageButtonClickListener(this::manageNotifications);
5604 setFooterView(footerView);
5607 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5608 private void inflateEmptyShadeView() {
5609 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
5610 R.layout.status_bar_no_notifications, this, false);
5611 view.setText(R.string.empty_shade_text);
5612 setEmptyShadeView(view);
5616 * Updates expanded, dimmed and locked states of notification rows.
5618 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5619 public void onUpdateRowStates() {
5620 changeViewPosition(mFooterView, -1);
5622 // The following views will be moved to the end of mStackScroller. This counter represents
5623 // the offset from the last child. Initialized to 1 for the very last position. It is post-
5624 // incremented in the following "changeViewPosition" calls so that its value is correct for
5625 // subsequent calls.
5626 int offsetFromEnd = 1;
5627 changeViewPosition(mEmptyShadeView,
5628 getChildCount() - offsetFromEnd++);
5630 // No post-increment for this call because it is the last one. Make sure to add one if
5631 // another "changeViewPosition" call is ever added.
5632 changeViewPosition(mShelf,
5633 getChildCount() - offsetFromEnd);
5636 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5637 public void setNotificationPanel(NotificationPanelView notificationPanelView) {
5638 mNotificationPanel = notificationPanelView;
5641 public void updateIconAreaViews() {
5642 mIconAreaController.updateNotificationIcons();
5646 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
5647 * notification positions accordingly.
5648 * @param height the new wake up height
5649 * @return the overflow how much the height is further than he lowest notification
5651 public float setPulseHeight(float height) {
5652 mAmbientState.setPulseHeight(height);
5653 if (mKeyguardBypassController.getBypassEnabled()) {
5654 notifyAppearChangedListeners();
5656 requestChildrenUpdate();
5657 return Math.max(0, height - mAmbientState.getInnerHeight(true /* ignorePulseHeight */));
5660 public float getPulseHeight() {
5661 return mAmbientState.getPulseHeight();
5665 * Set the amount how much we're dozing. This is different from how hidden the shade is, when
5666 * the notification is pulsing.
5668 public void setDozeAmount(float dozeAmount) {
5669 mAmbientState.setDozeAmount(dozeAmount);
5670 updateContinuousBackgroundDrawing();
5671 requestChildrenUpdate();
5674 public void wakeUpFromPulse() {
5675 setPulseHeight(getWakeUpHeight());
5676 // Let's place the hidden views at the end of the pulsing notification to make sure we have
5677 // a smooth animation
5678 boolean firstVisibleView = true;
5679 float wakeUplocation = -1f;
5680 int childCount = getChildCount();
5681 for (int i = 0; i < childCount; i++) {
5682 ExpandableView view = (ExpandableView) getChildAt(i);
5683 if (view.getVisibility() == View.GONE) {
5686 boolean isShelf = view == mShelf;
5687 if (!(view instanceof ExpandableNotificationRow) && !isShelf) {
5690 if (view.getVisibility() == View.VISIBLE && !isShelf) {
5691 if (firstVisibleView) {
5692 firstVisibleView = false;
5693 wakeUplocation = view.getTranslationY()
5694 + view.getActualHeight() - mShelf.getIntrinsicHeight();
5696 } else if (!firstVisibleView) {
5697 view.setTranslationY(wakeUplocation);
5700 mDimmedNeedsAnimation = true;
5704 public void onDynamicPrivacyChanged() {
5706 // The bottom might change because we're using the final actual height of the view
5707 mAnimateBottomOnLayout = true;
5709 // Let's update the footer once the notifications have been updated (in the next frame)
5712 updateSectionBoundaries();
5716 public void setOnPulseHeightChangedListener(Runnable listener) {
5717 mAmbientState.setOnPulseHeightChangedListener(listener);
5720 public float calculateAppearFractionBypass() {
5721 float pulseHeight = getPulseHeight();
5722 float wakeUpHeight = getWakeUpHeight();
5723 float dragDownAmount = pulseHeight - wakeUpHeight;
5725 // The total distance required to fully reveal the header
5726 float totalDistance = getIntrinsicPadding();
5727 return MathUtils.smoothStep(0, totalDistance, dragDownAmount);
5731 * A listener that is notified when the empty space below the notifications is clicked on
5733 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5734 public interface OnEmptySpaceClickListener {
5735 void onEmptySpaceClicked(float x, float y);
5739 * A listener that gets notified when the overscroll at the top has changed.
5741 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5742 public interface OnOverscrollTopChangedListener {
5745 * Notifies a listener that the overscroll has changed.
5747 * @param amount the amount of overscroll, in pixels
5748 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
5749 * unrubberbanded motion to directly expand overscroll view (e.g
5753 void onOverscrollTopChanged(float amount, boolean isRubberbanded);
5756 * Notify a listener that the scroller wants to escape from the scrolling motion and
5757 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
5759 * @param velocity The velocity that the Scroller had when over flinging
5760 * @param open Should the fling open or close the overscroll view.
5762 void flingTopOverscroll(float velocity, boolean open);
5765 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5766 public boolean hasActiveNotifications() {
5767 return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
5770 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5771 public void updateSpeedBumpIndex() {
5772 int speedBumpIndex = 0;
5773 int currentIndex = 0;
5774 final int N = getChildCount();
5775 for (int i = 0; i < N; i++) {
5776 View view = getChildAt(i);
5777 if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
5780 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
5782 boolean beforeSpeedBump;
5783 if (mHighPriorityBeforeSpeedBump) {
5784 beforeSpeedBump = row.getEntry().isTopBucket();
5786 beforeSpeedBump = !row.getEntry().ambient;
5788 if (beforeSpeedBump) {
5789 speedBumpIndex = currentIndex;
5792 boolean noAmbient = speedBumpIndex == N;
5793 updateSpeedBumpIndex(speedBumpIndex, noAmbient);
5796 /** Updates the indices of the boundaries between sections. */
5797 @ShadeViewRefactor(RefactorComponent.INPUT)
5798 public void updateSectionBoundaries() {
5799 mSectionsManager.updateSectionBoundaries();
5802 private void updateContinuousBackgroundDrawing() {
5803 boolean continuousBackground = !mAmbientState.isFullyAwake()
5804 && !mAmbientState.getDraggedViews().isEmpty();
5805 if (continuousBackground != mContinuousBackgroundUpdate) {
5806 mContinuousBackgroundUpdate = continuousBackground;
5807 if (continuousBackground) {
5808 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
5810 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
5815 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5816 private void updateContinuousShadowDrawing() {
5817 boolean continuousShadowUpdate = mAnimationRunning
5818 || !mAmbientState.getDraggedViews().isEmpty();
5819 if (continuousShadowUpdate != mContinuousShadowUpdate) {
5820 if (continuousShadowUpdate) {
5821 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
5823 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
5825 mContinuousShadowUpdate = continuousShadowUpdate;
5830 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5831 public void resetExposedMenuView(boolean animate, boolean force) {
5832 mSwipeHelper.resetExposedMenuView(animate, force);
5835 private static boolean matchesSelection(
5836 ExpandableNotificationRow row,
5837 @SelectedRows int selection) {
5838 switch (selection) {
5841 case ROWS_HIGH_PRIORITY:
5842 return row.getEntry().isTopBucket();
5844 return !row.getEntry().isTopBucket();
5846 throw new IllegalArgumentException("Unknown selection: " + selection);
5850 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5851 static class AnimationEvent {
5853 static AnimationFilter[] FILTERS = new AnimationFilter[]{
5855 // ANIMATION_TYPE_ADD
5856 new AnimationFilter()
5863 // ANIMATION_TYPE_REMOVE
5864 new AnimationFilter()
5871 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5872 new AnimationFilter()
5879 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5880 new AnimationFilter()
5887 // ANIMATION_TYPE_ACTIVATED_CHILD
5888 new AnimationFilter()
5891 // ANIMATION_TYPE_DIMMED
5892 new AnimationFilter()
5895 // ANIMATION_TYPE_CHANGE_POSITION
5896 new AnimationFilter()
5897 .animateAlpha() // maybe the children change positions
5903 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5904 new AnimationFilter()
5912 // ANIMATION_TYPE_HIDE_SENSITIVE
5913 new AnimationFilter()
5914 .animateHideSensitive(),
5916 // ANIMATION_TYPE_VIEW_RESIZE
5917 new AnimationFilter()
5923 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
5924 new AnimationFilter()
5931 // ANIMATION_TYPE_HEADS_UP_APPEAR
5932 new AnimationFilter()
5938 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
5939 new AnimationFilter()
5946 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
5947 new AnimationFilter()
5954 // ANIMATION_TYPE_HEADS_UP_OTHER
5955 new AnimationFilter()
5961 // ANIMATION_TYPE_EVERYTHING
5962 new AnimationFilter()
5965 .animateHideSensitive()
5972 static int[] LENGTHS = new int[]{
5974 // ANIMATION_TYPE_ADD
5975 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
5977 // ANIMATION_TYPE_REMOVE
5978 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
5980 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5981 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5983 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5984 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5986 // ANIMATION_TYPE_ACTIVATED_CHILD
5987 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5989 // ANIMATION_TYPE_DIMMED
5990 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5992 // ANIMATION_TYPE_CHANGE_POSITION
5993 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5995 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5996 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
5998 // ANIMATION_TYPE_HIDE_SENSITIVE
5999 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6001 // ANIMATION_TYPE_VIEW_RESIZE
6002 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6004 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
6005 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6007 // ANIMATION_TYPE_HEADS_UP_APPEAR
6008 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
6010 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
6011 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6013 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
6014 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6016 // ANIMATION_TYPE_HEADS_UP_OTHER
6017 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6019 // ANIMATION_TYPE_EVERYTHING
6020 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6023 static final int ANIMATION_TYPE_ADD = 0;
6024 static final int ANIMATION_TYPE_REMOVE = 1;
6025 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
6026 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
6027 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4;
6028 static final int ANIMATION_TYPE_DIMMED = 5;
6029 static final int ANIMATION_TYPE_CHANGE_POSITION = 6;
6030 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 7;
6031 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 8;
6032 static final int ANIMATION_TYPE_VIEW_RESIZE = 9;
6033 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 10;
6034 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 11;
6035 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 12;
6036 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
6037 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
6038 static final int ANIMATION_TYPE_EVERYTHING = 15;
6040 final long eventStartTime;
6041 final ExpandableView mChangingView;
6042 final int animationType;
6043 final AnimationFilter filter;
6045 View viewAfterChangingView;
6046 boolean headsUpFromBottom;
6048 AnimationEvent(ExpandableView view, int type) {
6049 this(view, type, LENGTHS[type]);
6052 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) {
6053 this(view, type, LENGTHS[type], filter);
6056 AnimationEvent(ExpandableView view, int type, long length) {
6057 this(view, type, length, FILTERS[type]);
6060 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) {
6061 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
6062 mChangingView = view;
6063 animationType = type;
6064 this.length = length;
6065 this.filter = filter;
6069 * Combines the length of several animation events into a single value.
6071 * @param events The events of the lengths to combine.
6072 * @return The combined length. Depending on the event types, this might be the maximum of
6073 * all events or the length of a specific event.
6075 static long combineLength(ArrayList<AnimationEvent> events) {
6077 int size = events.size();
6078 for (int i = 0; i < size; i++) {
6079 AnimationEvent event = events.get(i);
6080 length = Math.max(length, event.length);
6081 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
6082 return event.length;
6089 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
6090 private final StateListener mStateListener = new StateListener() {
6092 public void onStatePreChange(int oldState, int newState) {
6093 if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
6094 requestAnimateEverything();
6099 public void onStateChanged(int newState) {
6100 setStatusBarState(newState);
6104 public void onStatePostChange() {
6105 NotificationStackScrollLayout.this.onStatePostChange();
6110 @ShadeViewRefactor(RefactorComponent.INPUT)
6111 protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
6113 public void onMenuClicked(View view, int x, int y, MenuItem item) {
6114 if (mLongPressListener == null) {
6117 if (view instanceof ExpandableNotificationRow) {
6118 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6119 mMetricsLogger.write(row.getStatusBarNotification().getLogMaker()
6120 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
6121 .setType(MetricsEvent.TYPE_ACTION)
6124 mLongPressListener.onLongPress(view, x, y, item);
6128 public void onMenuReset(View row) {
6129 View translatingParentView = mSwipeHelper.getTranslatingParentView();
6130 if (translatingParentView != null && row == translatingParentView) {
6131 mSwipeHelper.clearExposedMenuView();
6132 mSwipeHelper.clearTranslatingParentView();
6133 if (row instanceof ExpandableNotificationRow) {
6134 mHeadsUpManager.setMenuShown(
6135 ((ExpandableNotificationRow) row).getEntry(), false);
6142 public void onMenuShown(View row) {
6143 if (row instanceof ExpandableNotificationRow) {
6144 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
6145 mMetricsLogger.write(notificationRow.getStatusBarNotification().getLogMaker()
6146 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
6147 .setType(MetricsEvent.TYPE_ACTION));
6148 mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
6149 mSwipeHelper.onMenuShown(row);
6150 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6151 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6152 false /* resetMenu */);
6154 // Check to see if we want to go directly to the notfication guts
6155 NotificationMenuRowPlugin provider = notificationRow.getProvider();
6156 if (provider.shouldShowGutsOnSnapOpen()) {
6157 MenuItem item = provider.menuItemToExposeOnSnap();
6159 Point origin = provider.getRevealAnimationOrigin();
6160 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
6162 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
6163 + "menu item in menuItemtoExposeOnSnap. Skipping.");
6166 // Close the menu row since we went directly to the guts
6167 resetExposedMenuView(false, true);
6173 @ShadeViewRefactor(RefactorComponent.INPUT)
6174 private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
6175 new NotificationSwipeHelper.NotificationCallback() {
6177 public void onDismiss() {
6178 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6179 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6180 false /* resetMenu */);
6184 public void onSnooze(StatusBarNotification sbn,
6185 NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
6186 mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
6190 public boolean shouldDismissQuickly() {
6191 return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
6195 public void onDragCancelled(View v) {
6196 setSwipingInProgress(false);
6197 mFalsingManager.onNotificatonStopDismissing();
6201 * Handles cleanup after the given {@code view} has been fully swiped out (including
6202 * re-invoking dismiss logic in case the notification has not made its way out yet).
6205 public void onChildDismissed(View view) {
6206 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6207 if (!row.isDismissed()) {
6208 handleChildViewDismissed(view);
6210 ViewGroup transientContainer = row.getTransientContainer();
6211 if (transientContainer != null) {
6212 transientContainer.removeTransientView(view);
6217 * Starts up notification dismiss and tells the notification, if any, to remove itself from
6220 * @param view view (e.g. notification) to dismiss from the layout
6223 public void handleChildViewDismissed(View view) {
6224 setSwipingInProgress(false);
6225 if (mDismissAllInProgress) {
6229 boolean isBlockingHelperShown = false;
6231 mAmbientState.onDragFinished(view);
6232 updateContinuousShadowDrawing();
6234 if (view instanceof ExpandableNotificationRow) {
6235 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6236 if (row.isHeadsUp()) {
6237 mHeadsUpManager.addSwipedOutNotification(
6238 row.getStatusBarNotification().getKey());
6240 isBlockingHelperShown =
6241 row.performDismissWithBlockingHelper(false /* fromAccessibility */);
6244 if (!isBlockingHelperShown) {
6245 mSwipedOutViews.add(view);
6247 mFalsingManager.onNotificationDismissed();
6248 if (mFalsingManager.shouldEnforceBouncer()) {
6249 mStatusBar.executeRunnableDismissingKeyguard(
6251 null /* cancelAction */,
6252 false /* dismissShade */,
6253 true /* afterKeyguardGone */,
6254 false /* deferred */);
6259 public boolean isAntiFalsingNeeded() {
6260 return onKeyguard();
6264 public View getChildAtPosition(MotionEvent ev) {
6265 View child = NotificationStackScrollLayout.this.getChildAtPosition(ev.getX(),
6267 if (child instanceof ExpandableNotificationRow) {
6268 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
6269 ExpandableNotificationRow parent = row.getNotificationParent();
6270 if (parent != null && parent.areChildrenExpanded()
6271 && (parent.areGutsExposed()
6272 || mSwipeHelper.getExposedMenuView() == parent
6273 || (parent.getNotificationChildren().size() == 1
6274 && parent.getEntry().isClearable()))) {
6275 // In this case the group is expanded and showing the menu for the
6276 // group, further interaction should apply to the group, not any
6277 // child notifications so we use the parent of the child. We also do the same
6278 // if we only have a single child.
6286 public void onBeginDrag(View v) {
6287 mFalsingManager.onNotificatonStartDismissing();
6288 setSwipingInProgress(true);
6289 mAmbientState.onBeginDrag((ExpandableView) v);
6290 updateContinuousShadowDrawing();
6291 updateContinuousBackgroundDrawing();
6292 requestChildrenUpdate();
6296 public void onChildSnappedBack(View animView, float targetLeft) {
6297 mAmbientState.onDragFinished(animView);
6298 updateContinuousShadowDrawing();
6299 updateContinuousBackgroundDrawing();
6300 if (animView instanceof ExpandableNotificationRow) {
6301 ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
6302 if (row.isPinned() && !canChildBeDismissed(row)
6303 && row.getStatusBarNotification().getNotification().fullScreenIntent
6305 mHeadsUpManager.removeNotification(row.getStatusBarNotification().getKey(),
6306 true /* removeImmediately */);
6312 public boolean updateSwipeProgress(View animView, boolean dismissable,
6313 float swipeProgress) {
6314 // Returning true prevents alpha fading.
6315 return !mFadeNotificationsOnDismiss;
6319 public float getFalsingThresholdFactor() {
6320 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
6324 public int getConstrainSwipeStartPosition() {
6325 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
6326 if (menuRow != null) {
6327 return Math.abs(menuRow.getMenuSnapTarget());
6333 public boolean canChildBeDismissed(View v) {
6334 return StackScrollAlgorithm.canChildBeDismissed(v);
6338 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
6339 //TODO: b/131242807 for why this doesn't do anything with direction
6340 return canChildBeDismissed(v);
6344 // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
6346 @ShadeViewRefactor(RefactorComponent.INPUT)
6347 private final DragDownCallback mDragDownCallback = new DragDownCallback() {
6349 /* Only ever called as a consequence of a lockscreen expansion gesture. */
6351 public boolean onDraggedDown(View startingChild, int dragLengthY) {
6352 if (mStatusBarState == StatusBarState.KEYGUARD
6353 && hasActiveNotifications()) {
6354 mLockscreenGestureLogger.write(
6355 MetricsEvent.ACTION_LS_SHADE,
6356 (int) (dragLengthY / mDisplayMetrics.density),
6357 0 /* velocityDp - N/A */);
6359 if (!mAmbientState.isDozing() || startingChild != null) {
6360 // We have notifications, go to locked shade.
6361 mShadeController.goToLockedShade(startingChild);
6362 if (startingChild instanceof ExpandableNotificationRow) {
6363 ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
6364 row.onExpandedByGesture(true /* drag down is always an open */);
6369 } else if (mDynamicPrivacyController.isInLockedDownShade()) {
6370 mStatusbarStateController.setLeaveOpenOnKeyguardHide(true);
6371 mStatusBar.dismissKeyguardThenExecute(() -> false /* dismissAction */,
6372 null /* cancelRunnable */, false /* afterKeyguardGone */);
6381 public void onDragDownReset() {
6382 setDimmed(true /* dimmed */, true /* animated */);
6383 resetScrollPosition();
6384 resetCheckSnoozeLeavebehind();
6388 public void onCrossedThreshold(boolean above) {
6389 setDimmed(!above /* dimmed */, true /* animate */);
6393 public void onTouchSlopExceeded() {
6395 checkSnoozeLeavebehind();
6399 public void setEmptyDragAmount(float amount) {
6400 mNotificationPanel.setEmptyDragAmount(amount);
6404 public boolean isFalsingCheckNeeded() {
6405 return mStatusBarState == StatusBarState.KEYGUARD;
6409 public boolean isDragDownEnabledForView(ExpandableView view) {
6410 if (isDragDownAnywhereEnabled()) {
6413 if (mDynamicPrivacyController.isInLockedDownShade()) {
6415 // Dragging down is allowed in general
6418 if (view instanceof ExpandableNotificationRow) {
6419 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
6420 return ((ExpandableNotificationRow) view).getEntry().isSensitive();
6427 public boolean isDragDownAnywhereEnabled() {
6428 return mStatusbarStateController.getState() == StatusBarState.KEYGUARD
6429 && !mKeyguardBypassController.getBypassEnabled();
6433 public DragDownCallback getDragDownCallback() { return mDragDownCallback; }
6435 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6436 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() {
6438 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6439 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6443 public boolean isExpanded() {
6448 public Context getContext() {
6453 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
6456 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6457 private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
6459 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
6460 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
6461 && (mIsExpanded || changedRow.isPinned());
6463 mExpandedGroupView = changedRow;
6464 mNeedsAnimation = true;
6466 changedRow.setChildrenExpanded(expanded, animated);
6467 if (!mGroupExpandedForMeasure) {
6468 onHeightChanged(changedRow, false /* needsAnimation */);
6470 runAfterAnimationFinished(new Runnable() {
6473 changedRow.onFinishedExpansionChange();
6479 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
6480 mStatusBar.requestNotificationUpdate();
6484 public void onGroupsChanged() {
6485 mStatusBar.requestNotificationUpdate();
6489 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6490 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
6492 public ExpandableView getChildAtPosition(float touchX, float touchY) {
6493 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
6497 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6498 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6502 public boolean canChildBeExpanded(View v) {
6503 return v instanceof ExpandableNotificationRow
6504 && ((ExpandableNotificationRow) v).isExpandable()
6505 && !((ExpandableNotificationRow) v).areGutsExposed()
6506 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
6509 /* Only ever called as a consequence of an expansion gesture in the shade. */
6511 public void setUserExpandedChild(View v, boolean userExpanded) {
6512 if (v instanceof ExpandableNotificationRow) {
6513 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
6514 if (userExpanded && onKeyguard()) {
6515 // Due to a race when locking the screen while touching, a notification may be
6516 // expanded even after we went back to keyguard. An example of this happens if
6517 // you click in the empty space while expanding a group.
6519 // We also need to un-user lock it here, since otherwise the content height
6520 // calculated might be wrong. We also can't invert the two calls since
6521 // un-userlocking it will trigger a layout switch in the content view.
6522 row.setUserLocked(false);
6523 updateContentHeight();
6524 notifyHeightChangeListener(row);
6527 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
6528 row.onExpandedByGesture(userExpanded);
6533 public void setExpansionCancelled(View v) {
6534 if (v instanceof ExpandableNotificationRow) {
6535 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
6540 public void setUserLockedChild(View v, boolean userLocked) {
6541 if (v instanceof ExpandableNotificationRow) {
6542 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
6545 requestDisallowInterceptTouchEvent(true);
6549 public void expansionStateChanged(boolean isExpanding) {
6550 mExpandingNotification = isExpanding;
6551 if (!mExpandedInThisMotion) {
6552 if (ANCHOR_SCROLLING) {
6555 mMaxScrollAfterExpand = mOwnScrollY;
6557 mExpandedInThisMotion = true;
6562 public int getMaxExpandHeight(ExpandableView view) {
6563 return view.getMaxContentHeight();
6567 public ExpandHelper.Callback getExpandHelperCallback() {
6568 return mExpandHelperCallback;
6571 /** Enum for selecting some or all notification rows (does not included non-notif views). */
6573 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
6574 public @interface SelectedRows {}
6575 /** All rows representing notifs. */
6576 public static final int ROWS_ALL = 0;
6577 /** Only rows where entry.isHighPriority() is true. */
6578 public static final int ROWS_HIGH_PRIORITY = 1;
6579 /** Only rows where entry.isHighPriority() is false. */
6580 public static final int ROWS_GENTLE = 2;