blob: 541c7845a5d320abdeda037d48dc38373ca70425 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinek67b22602014-03-10 15:40:16 +010018
Evan Laird25f02752019-08-14 19:25:06 -040019import android.annotation.NonNull;
20import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010021import android.content.Context;
Anthony Chen9fe1ee72017-04-07 13:53:37 -070022import android.content.res.Resources;
Christoph Studer6e3eceb2014-04-01 18:40:27 +020023import android.util.Log;
Steve Elliott0c904c32020-06-22 11:50:35 -040024import android.util.MathUtils;
Selim Cinek67b22602014-03-10 15:40:16 +010025import android.view.View;
26import android.view.ViewGroup;
Gus Prevase2d6f042018-10-17 15:25:30 -040027
Selim Cinek67b22602014-03-10 15:40:16 +010028import com.android.systemui.R;
Selim Cinekcde90e52016-12-22 21:01:49 +010029import com.android.systemui.statusbar.EmptyShadeView;
Gus Prevase2d6f042018-10-17 15:25:30 -040030import com.android.systemui.statusbar.NotificationShelf;
31import com.android.systemui.statusbar.notification.NotificationUtils;
Rohan Shah20790b82018-07-02 17:21:04 -070032import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
33import com.android.systemui.statusbar.notification.row.ExpandableView;
34import com.android.systemui.statusbar.notification.row.FooterView;
Selim Cinek67b22602014-03-10 15:40:16 +010035
Jorim Jaggid4a57442014-04-10 02:45:55 +020036import java.util.ArrayList;
Selim Cinek42357e02016-02-24 18:48:01 -080037import java.util.HashMap;
Selim Cinekb5605e52015-02-20 18:21:41 +010038import java.util.List;
Jorim Jaggid4a57442014-04-10 02:45:55 +020039
Selim Cinek67b22602014-03-10 15:40:16 +010040/**
Rohan Shah20790b82018-07-02 17:21:04 -070041 * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
Selim Cinek67b22602014-03-10 15:40:16 +010042 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
43 * .stack.StackScrollState}
44 */
45public class StackScrollAlgorithm {
46
Gus Prevas0fa58d62019-01-11 13:58:40 -050047 static final boolean ANCHOR_SCROLLING = false;
48
Christoph Studer6e3eceb2014-04-01 18:40:27 +020049 private static final String LOG_TAG = "StackScrollAlgorithm";
Dave Mankoffa4d195d2018-11-16 13:33:27 -050050 private final ViewGroup mHostView;
Christoph Studer6e3eceb2014-04-01 18:40:27 +020051
Selim Cinek67b22602014-03-10 15:40:16 +010052 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -080053 private int mIncreasedPaddingBetweenElements;
Gus Prevase2d6f042018-10-17 15:25:30 -040054 private int mGapHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010055 private int mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +010056
Selim Cinek67b22602014-03-10 15:40:16 +010057 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
Selim Cinek1685e632014-04-08 02:27:49 +020058 private boolean mIsExpanded;
Anthony Chen9fe1ee72017-04-07 13:53:37 -070059 private boolean mClipNotificationScrollToTop;
Selim Cinekcafa87f2016-10-26 17:00:17 -070060 private int mStatusBarHeight;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080061 private float mHeadsUpInset;
Selim Cinek99e9adf2018-03-15 09:17:47 -070062 private int mPinnedZTranslationExtra;
Selim Cinek67b22602014-03-10 15:40:16 +010063
Ned Burns9eb06332019-04-23 16:02:12 -040064 public StackScrollAlgorithm(
65 Context context,
66 ViewGroup hostView) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -050067 mHostView = hostView;
Selim Cinekaf0dc312015-12-15 17:01:44 -080068 initView(context);
Selim Cinek34c0a8d2014-05-12 00:01:43 +020069 }
70
Selim Cinekaf0dc312015-12-15 17:01:44 -080071 public void initView(Context context) {
72 initConstants(context);
Selim Cinek34c0a8d2014-05-12 00:01:43 +020073 }
74
Selim Cinek67b22602014-03-10 15:40:16 +010075 private void initConstants(Context context) {
Anthony Chen9fe1ee72017-04-07 13:53:37 -070076 Resources res = context.getResources();
77 mPaddingBetweenElements = res.getDimensionPixelSize(
Selim Cinekdb167372016-11-17 15:41:17 -080078 R.dimen.notification_divider_height);
Anthony Chen9fe1ee72017-04-07 13:53:37 -070079 mIncreasedPaddingBetweenElements =
80 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
81 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
82 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
83 mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
Selim Cinekaa9db1f2018-02-27 17:35:47 -080084 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
85 R.dimen.heads_up_status_bar_padding);
Selim Cinek99e9adf2018-03-15 09:17:47 -070086 mPinnedZTranslationExtra = res.getDimensionPixelSize(
87 R.dimen.heads_up_pinned_elevation);
Gus Prevase2d6f042018-10-17 15:25:30 -040088 mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
Jorim Jaggid7c1fae2014-08-13 18:27:47 +020089 }
Selim Cinek67b22602014-03-10 15:40:16 +010090
Dave Mankoffa4d195d2018-11-16 13:33:27 -050091 /**
92 * Updates the state of all children in the hostview based on this algorithm.
93 */
94 public void resetViewStates(AmbientState ambientState) {
Selim Cinek67b22602014-03-10 15:40:16 +010095 // The state of the local variables are saved in an algorithmState to easily subdivide it
96 // into multiple phases.
97 StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
98
99 // First we reset the view states to their default values.
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500100 resetChildViewStates();
Selim Cinek67b22602014-03-10 15:40:16 +0100101
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500102 initAlgorithmState(mHostView, algorithmState, ambientState);
Selim Cinek1408eb52014-06-02 14:45:38 +0200103
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500104 updatePositionsForState(algorithmState, ambientState);
Selim Cinek67b22602014-03-10 15:40:16 +0100105
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500106 updateZValuesForState(algorithmState, ambientState);
Selim Cinek3776fe02016-02-04 13:32:43 -0800107
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500108 updateHeadsUpStates(algorithmState, ambientState);
Selim Cinek5040f2e2019-02-14 18:22:42 -0800109 updatePulsingStates(algorithmState, ambientState);
Selim Cinekeb973562014-05-02 17:07:49 +0200110
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500111 updateDimmedActivatedHideSensitive(ambientState, algorithmState);
112 updateClipping(algorithmState, ambientState);
113 updateSpeedBumpState(algorithmState, ambientState);
114 updateShelfState(ambientState);
115 getNotificationChildrenStates(algorithmState, ambientState);
Selim Cinekb5605e52015-02-20 18:21:41 +0100116 }
117
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500118 private void resetChildViewStates() {
119 int numChildren = mHostView.getChildCount();
120 for (int i = 0; i < numChildren; i++) {
121 ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
122 child.resetViewState();
123 }
124 }
125
126 private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
Selim Cinekc25989e2018-02-16 16:42:14 -0800127 AmbientState ambientState) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100128 int childCount = algorithmState.visibleChildren.size();
129 for (int i = 0; i < childCount; i++) {
130 ExpandableView v = algorithmState.visibleChildren.get(i);
131 if (v instanceof ExpandableNotificationRow) {
132 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500133 row.updateChildrenStates(ambientState);
Selim Cinekb5605e52015-02-20 18:21:41 +0100134 }
135 }
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200136 }
137
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500138 private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
139 AmbientState ambientState) {
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200140 int childCount = algorithmState.visibleChildren.size();
Selim Cinekdb167372016-11-17 15:41:17 -0800141 int belowSpeedBump = ambientState.getSpeedBumpIndex();
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200142 for (int i = 0; i < childCount; i++) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500143 ExpandableView child = algorithmState.visibleChildren.get(i);
144 ExpandableViewState childViewState = child.getViewState();
Selim Cinek3107cfa2014-07-22 15:24:29 +0200145
146 // The speed bump can also be gone, so equality needs to be taken when comparing
147 // indices.
Selim Cinekdb167372016-11-17 15:41:17 -0800148 childViewState.belowSpeedBump = i >= belowSpeedBump;
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200149 }
Selim Cinekdb167372016-11-17 15:41:17 -0800150
151 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500152
153 private void updateShelfState(AmbientState ambientState) {
Selim Cinek281c2022016-10-13 19:14:43 -0700154 NotificationShelf shelf = ambientState.getShelf();
Eliot Courtney5d3d2d02018-01-18 15:59:03 +0900155 if (shelf != null) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500156 shelf.updateState(ambientState);
Eliot Courtney5d3d2d02018-01-18 15:59:03 +0900157 }
Selim Cinekf54090e2014-06-17 17:24:51 -0700158 }
159
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500160 private void updateClipping(StackScrollAlgorithmState algorithmState,
161 AmbientState ambientState) {
Selim Cinek355652a2016-12-07 13:32:12 -0800162 float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
Selim Cinek2627d722018-01-19 12:16:49 -0800163 + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange()
164 : 0;
Selim Cinek8f3f03f2019-07-18 14:19:54 -0700165 float clipStart = 0;
Selim Cinek708a6c12014-05-28 14:16:02 +0200166 int childCount = algorithmState.visibleChildren.size();
Selim Cinek3ff6f502019-07-29 16:33:48 -0700167 boolean firstHeadsUp = true;
Selim Cinek708a6c12014-05-28 14:16:02 +0200168 for (int i = 0; i < childCount; i++) {
169 ExpandableView child = algorithmState.visibleChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500170 ExpandableViewState state = child.getViewState();
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100171 if (!child.mustStayOnScreen() || state.headsUpIsVisible) {
Selim Cinek8f3f03f2019-07-18 14:19:54 -0700172 clipStart = Math.max(drawStart, clipStart);
Selim Cinek3776fe02016-02-04 13:32:43 -0800173 }
Selim Cinek587cbf32016-01-19 11:36:18 -0800174 float newYTranslation = state.yTranslation;
175 float newHeight = state.height;
Selim Cinek708a6c12014-05-28 14:16:02 +0200176 float newNotificationEnd = newYTranslation + newHeight;
Mady Mellor53ac1ef2016-06-20 13:11:38 -0700177 boolean isHeadsUp = (child instanceof ExpandableNotificationRow)
178 && ((ExpandableNotificationRow) child).isPinned();
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700179 if (mClipNotificationScrollToTop
Selim Cinek3ff6f502019-07-29 16:33:48 -0700180 && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
Selim Cinek8f3f03f2019-07-18 14:19:54 -0700181 && newYTranslation < clipStart) {
Mady Mellorc128f222016-04-26 11:42:46 -0700182 // The previous view is overlapping on top, clip!
Selim Cinek8f3f03f2019-07-18 14:19:54 -0700183 float overlapAmount = clipStart - newYTranslation;
Mady Mellorc128f222016-04-26 11:42:46 -0700184 state.clipTopAmount = (int) overlapAmount;
Selim Cinekc5baa3e2014-10-29 19:04:19 +0100185 } else {
Mady Mellorc128f222016-04-26 11:42:46 -0700186 state.clipTopAmount = 0;
Selim Cinek9c17b772015-07-07 20:37:09 -0700187 }
Selim Cinek3ff6f502019-07-29 16:33:48 -0700188 if (isHeadsUp) {
189 firstHeadsUp = false;
190 }
Selim Cinek708a6c12014-05-28 14:16:02 +0200191 if (!child.isTransparent()) {
192 // Only update the previous values if we are not transparent,
193 // otherwise we would clip to a transparent view.
Selim Cinek8f3f03f2019-07-18 14:19:54 -0700194 clipStart = Math.max(clipStart, isHeadsUp ? newYTranslation : newNotificationEnd);
Selim Cinek708a6c12014-05-28 14:16:02 +0200195 }
196 }
197 }
198
199 /**
Jorim Jaggiae441282014-08-01 02:45:18 +0200200 * Updates the dimmed, activated and hiding sensitive states of the children.
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200201 */
Jorim Jaggiae441282014-08-01 02:45:18 +0200202 private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500203 StackScrollAlgorithmState algorithmState) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200204 boolean dimmed = ambientState.isDimmed();
Jorim Jaggiae441282014-08-01 02:45:18 +0200205 boolean hideSensitive = ambientState.isHideSensitive();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200206 View activatedChild = ambientState.getActivatedChild();
207 int childCount = algorithmState.visibleChildren.size();
208 for (int i = 0; i < childCount; i++) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500209 ExpandableView child = algorithmState.visibleChildren.get(i);
210 ExpandableViewState childViewState = child.getViewState();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200211 childViewState.dimmed = dimmed;
Jorim Jaggiae441282014-08-01 02:45:18 +0200212 childViewState.hideSensitive = hideSensitive;
Selim Cinekb89de4e2014-06-10 10:47:05 +0200213 boolean isActivatedChild = activatedChild == child;
Jorim Jaggi4538cee2014-09-09 15:21:38 +0200214 if (dimmed && isActivatedChild) {
Selim Cinek281c2022016-10-13 19:14:43 -0700215 childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200216 }
217 }
Selim Cinekeb973562014-05-02 17:07:49 +0200218 }
219
220 /**
Selim Cinek61633a82016-01-25 15:54:10 -0800221 * Initialize the algorithm state like updating the visible children.
Jorim Jaggid4a57442014-04-10 02:45:55 +0200222 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500223 private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
Selim Cinek3776fe02016-02-04 13:32:43 -0800224 AmbientState ambientState) {
Selim Cinek3776fe02016-02-04 13:32:43 -0800225 float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
226
227 int scrollY = ambientState.getScrollY();
228
229 // Due to the overScroller, the stackscroller can have negative scroll state. This is
230 // already accounted for by the top padding and doesn't need an additional adaption
231 scrollY = Math.max(0, scrollY);
232 state.scrollY = (int) (scrollY + bottomOverScroll);
233
Gus Prevas0fa58d62019-01-11 13:58:40 -0500234 if (ANCHOR_SCROLLING) {
235 state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll);
236 }
237
Selim Cinek3776fe02016-02-04 13:32:43 -0800238 //now init the visible children and update paddings
Jorim Jaggid4a57442014-04-10 02:45:55 +0200239 int childCount = hostView.getChildCount();
240 state.visibleChildren.clear();
241 state.visibleChildren.ensureCapacity(childCount);
Selim Cineka7ed2c12017-01-23 20:47:24 -0800242 state.paddingMap.clear();
Selim Cinekb036ca42015-02-20 15:56:28 +0100243 int notGoneIndex = 0;
Selim Cinek61633a82016-01-25 15:54:10 -0800244 ExpandableView lastView = null;
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700245 int firstHiddenIndex = ambientState.isDozing()
Selim Cinekd96ed402017-07-28 18:19:03 -0700246 ? (ambientState.hasPulsingNotifications() ? 1 : 0)
247 : childCount;
248
249 // The goal here is to fill the padding map, by iterating over how much padding each child
250 // needs. The map is thereby reused, by first filling it with the padding amount and when
251 // iterating over it again, it's filled with the actual resolved value.
252
Jorim Jaggid4a57442014-04-10 02:45:55 +0200253 for (int i = 0; i < childCount; i++) {
Gus Prevas0fa58d62019-01-11 13:58:40 -0500254 if (ANCHOR_SCROLLING) {
255 if (i == ambientState.getAnchorViewIndex()) {
256 state.anchorViewIndex = state.visibleChildren.size();
257 }
258 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200259 ExpandableView v = (ExpandableView) hostView.getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +0200260 if (v.getVisibility() != View.GONE) {
Selim Cinek281c2022016-10-13 19:14:43 -0700261 if (v == ambientState.getShelf()) {
262 continue;
263 }
Selim Cinekd96ed402017-07-28 18:19:03 -0700264 if (i >= firstHiddenIndex) {
265 // we need normal padding now, to be in sync with what the stack calculates
266 lastView = null;
Selim Cinekd96ed402017-07-28 18:19:03 -0700267 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500268 notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v);
Selim Cinekd96ed402017-07-28 18:19:03 -0700269 float increasedPadding = v.getIncreasedPaddingAmount();
Selim Cinek42357e02016-02-24 18:48:01 -0800270 if (increasedPadding != 0.0f) {
Selim Cineka7ed2c12017-01-23 20:47:24 -0800271 state.paddingMap.put(v, increasedPadding);
Selim Cinek61633a82016-01-25 15:54:10 -0800272 if (lastView != null) {
Selim Cineka7ed2c12017-01-23 20:47:24 -0800273 Float prevValue = state.paddingMap.get(lastView);
274 float newValue = getPaddingForValue(increasedPadding);
275 if (prevValue != null) {
276 float prevPadding = getPaddingForValue(prevValue);
277 if (increasedPadding > 0) {
278 newValue = NotificationUtils.interpolate(
279 prevPadding,
280 newValue,
281 increasedPadding);
282 } else if (prevValue > 0) {
283 newValue = NotificationUtils.interpolate(
284 newValue,
285 prevPadding,
286 prevValue);
287 }
288 }
289 state.paddingMap.put(lastView, newValue);
Selim Cinek61633a82016-01-25 15:54:10 -0800290 }
Selim Cineka7ed2c12017-01-23 20:47:24 -0800291 } else if (lastView != null) {
Selim Cinekd96ed402017-07-28 18:19:03 -0700292
293 // Let's now resolve the value to an actual padding
Selim Cineka7ed2c12017-01-23 20:47:24 -0800294 float newValue = getPaddingForValue(state.paddingMap.get(lastView));
295 state.paddingMap.put(lastView, newValue);
Selim Cinek61633a82016-01-25 15:54:10 -0800296 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100297 if (v instanceof ExpandableNotificationRow) {
298 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700299
300 // handle the notgoneIndex for the children as well
Kevin Han43077f92020-02-28 12:51:53 -0800301 List<ExpandableNotificationRow> children = row.getAttachedChildren();
Selim Cinek83bc7832015-10-22 13:26:54 -0700302 if (row.isSummaryWithChildren() && children != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100303 for (ExpandableNotificationRow childRow : children) {
304 if (childRow.getVisibility() != View.GONE) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500305 ExpandableViewState childState = childRow.getViewState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100306 childState.notGoneIndex = notGoneIndex;
307 notGoneIndex++;
308 }
309 }
310 }
311 }
Selim Cinek61633a82016-01-25 15:54:10 -0800312 lastView = v;
Jorim Jaggid4a57442014-04-10 02:45:55 +0200313 }
314 }
Selim Cinek2627d722018-01-19 12:16:49 -0800315 ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
316 state.indexOfExpandingNotification = expandingNotification != null
Selim Cinekc25989e2018-02-16 16:42:14 -0800317 ? expandingNotification.isChildInGroup()
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500318 ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
319 : state.visibleChildren.indexOf(expandingNotification)
Selim Cinek2627d722018-01-19 12:16:49 -0800320 : -1;
Jorim Jaggid4a57442014-04-10 02:45:55 +0200321 }
322
Selim Cineka7ed2c12017-01-23 20:47:24 -0800323 private float getPaddingForValue(Float increasedPadding) {
324 if (increasedPadding == null) {
325 return mPaddingBetweenElements;
326 } else if (increasedPadding >= 0.0f) {
327 return NotificationUtils.interpolate(
328 mPaddingBetweenElements,
329 mIncreasedPaddingBetweenElements,
330 increasedPadding);
331 } else {
332 return NotificationUtils.interpolate(
333 0,
334 mPaddingBetweenElements,
335 1.0f + increasedPadding);
336 }
337 }
338
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500339 private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700340 ExpandableView v) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500341 ExpandableViewState viewState = v.getViewState();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700342 viewState.notGoneIndex = notGoneIndex;
343 state.visibleChildren.add(v);
344 notGoneIndex++;
345 return notGoneIndex;
346 }
347
Jorim Jaggid4a57442014-04-10 02:45:55 +0200348 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100349 * Determine the positions for the views. This is the main part of the algorithm.
350 *
Selim Cinek67b22602014-03-10 15:40:16 +0100351 * @param algorithmState The state in which the current pass of the algorithm is currently in
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500352 * @param ambientState The current ambient state
Selim Cinek67b22602014-03-10 15:40:16 +0100353 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500354 private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
355 AmbientState ambientState) {
Gus Prevas0fa58d62019-01-11 13:58:40 -0500356 if (ANCHOR_SCROLLING) {
357 float currentYPosition = algorithmState.anchorViewY;
358 int childCount = algorithmState.visibleChildren.size();
359 for (int i = algorithmState.anchorViewIndex; i < childCount; i++) {
Gus Prevas0fa58d62019-01-11 13:58:40 -0500360 currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
361 false /* reverse */);
362 }
363 currentYPosition = algorithmState.anchorViewY;
364 for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) {
Gus Prevas0fa58d62019-01-11 13:58:40 -0500365 currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
366 true /* reverse */);
367 }
368 } else {
369 // The y coordinate of the current child.
370 float currentYPosition = -algorithmState.scrollY;
371 int childCount = algorithmState.visibleChildren.size();
372 for (int i = 0; i < childCount; i++) {
Gus Prevas0fa58d62019-01-11 13:58:40 -0500373 currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
374 false /* reverse */);
375 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700376 }
377 }
378
Gus Prevas0fa58d62019-01-11 13:58:40 -0500379 /**
380 * Populates the {@link ExpandableViewState} for a single child.
381 *
382 * @param i The index of the child in
383 * {@link StackScrollAlgorithmState#visibleChildren}.
384 * @param algorithmState The overall output state of the algorithm.
385 * @param ambientState The input state provided to the algorithm.
386 * @param currentYPosition The Y position of the current pass of the algorithm. For a forward
387 * pass, this should be the top of the child; for a reverse pass, the
388 * bottom of the child.
389 * @param reverse Whether we're laying out children in the reverse direction (Y
390 * positions
391 * decreasing) instead of the forward direction (Y positions
392 * increasing).
393 * @return The Y position after laying out the child. This will be the {@code currentYPosition}
394 * for the next call to this method, after adjusting for any gaps between children.
395 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500396 protected float updateChild(
397 int i,
398 StackScrollAlgorithmState algorithmState,
399 AmbientState ambientState,
Gus Prevas0fa58d62019-01-11 13:58:40 -0500400 float currentYPosition,
401 boolean reverse) {
Muyuan Li87798022016-04-07 17:51:25 -0700402 ExpandableView child = algorithmState.visibleChildren.get(i);
Evan Laird25f02752019-08-14 19:25:06 -0400403 ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
Ned Burns9eb06332019-04-23 16:02:12 -0400404 final boolean applyGapHeight =
Evan Laird25f02752019-08-14 19:25:06 -0400405 childNeedsGapHeight(
Selim Cinek54ef7be2020-05-27 19:09:21 -0700406 ambientState.getSectionProvider(), algorithmState.anchorViewIndex, i,
407 child, previousChild);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500408 ExpandableViewState childViewState = child.getViewState();
Selim Cinekbbcebde2016-11-09 18:28:20 -0800409 childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
Ned Burns9eb06332019-04-23 16:02:12 -0400410
411 if (applyGapHeight && !reverse) {
412 currentYPosition += mGapHeight;
413 }
414
Muyuan Li87798022016-04-07 17:51:25 -0700415 int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
416 int childHeight = getMaxAllowedChildHeight(child);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500417 if (reverse) {
418 childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild);
419 if (currentYPosition <= 0) {
420 childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
421 }
422 } else {
423 childViewState.yTranslation = currentYPosition;
Gus Prevase2d6f042018-10-17 15:25:30 -0400424 }
Julia Reynoldsed1c9af2018-03-21 15:21:09 -0400425 boolean isFooterView = child instanceof FooterView;
Selim Cinekcde90e52016-12-22 21:01:49 +0100426 boolean isEmptyShadeView = child instanceof EmptyShadeView;
Muyuan Li87798022016-04-07 17:51:25 -0700427
Selim Cinekdb167372016-11-17 15:41:17 -0800428 childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100429 float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
Selim Cinekc25989e2018-02-16 16:42:14 -0800430 if (i <= algorithmState.getIndexOfExpandingNotification()) {
Selim Cinek2627d722018-01-19 12:16:49 -0800431 inset += ambientState.getExpandAnimationTopChange();
432 }
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100433 if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
434 // Even if we're not scrolled away we're in view and we're also not in the
435 // shelf. We can relax the constraints and let us scroll off the top!
436 float end = childViewState.yTranslation + childViewState.height + inset;
437 childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
438 }
Julia Reynoldsed1c9af2018-03-21 15:21:09 -0400439 if (isFooterView) {
Selim Cinekdb167372016-11-17 15:41:17 -0800440 childViewState.yTranslation = Math.min(childViewState.yTranslation,
441 ambientState.getInnerHeight() - childHeight);
Selim Cinekcde90e52016-12-22 21:01:49 +0100442 } else if (isEmptyShadeView) {
443 childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
444 + ambientState.getStackTranslation() * 0.25f;
Steve Elliott0c904c32020-06-22 11:50:35 -0400445 } else if (child != ambientState.getTrackedHeadsUpRow()) {
Selim Cinek2627d722018-01-19 12:16:49 -0800446 clampPositionToShelf(child, childViewState, ambientState);
Muyuan Li87798022016-04-07 17:51:25 -0700447 }
448
Gus Prevas0fa58d62019-01-11 13:58:40 -0500449 if (reverse) {
450 currentYPosition = childViewState.yTranslation;
Ned Burns9eb06332019-04-23 16:02:12 -0400451 if (applyGapHeight) {
452 currentYPosition -= mGapHeight;
453 }
Gus Prevas0fa58d62019-01-11 13:58:40 -0500454 } else {
455 currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
456 if (currentYPosition <= 0) {
457 childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
458 }
Muyuan Li87798022016-04-07 17:51:25 -0700459 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800460 if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
Muyuan Li87798022016-04-07 17:51:25 -0700461 Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
462 }
463
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100464 childViewState.yTranslation += inset;
Muyuan Li87798022016-04-07 17:51:25 -0700465 return currentYPosition;
466 }
467
Selim Cinek54ef7be2020-05-27 19:09:21 -0700468 /**
469 * Get the gap height needed for before a view
470 *
471 * @param sectionProvider the sectionProvider used to understand the sections
472 * @param anchorViewIndex the anchorView index when anchor scrolling, can be 0 if not
473 * @param visibleIndex the visible index of this view in the list
474 * @param child the child asked about
475 * @param previousChild the child right before it or null if none
476 * @return the size of the gap needed or 0 if none is needed
477 */
478 public float getGapHeightForChild(
479 SectionProvider sectionProvider,
480 int anchorViewIndex,
481 int visibleIndex,
482 View child,
483 View previousChild) {
484
485 if (childNeedsGapHeight(sectionProvider, anchorViewIndex, visibleIndex, child,
486 previousChild)) {
487 return mGapHeight;
488 } else {
489 return 0;
490 }
491 }
492
493 /**
494 * Does a given child need a gap, i.e spacing before a view?
495 *
496 * @param sectionProvider the sectionProvider used to understand the sections
497 * @param anchorViewIndex the anchorView index when anchor scrolling, can be 0 if not
498 * @param visibleIndex the visible index of this view in the list
499 * @param child the child asked about
500 * @param previousChild the child right before it or null if none
501 * @return if the child needs a gap height
502 */
Ned Burns9eb06332019-04-23 16:02:12 -0400503 private boolean childNeedsGapHeight(
504 SectionProvider sectionProvider,
Selim Cinek54ef7be2020-05-27 19:09:21 -0700505 int anchorViewIndex,
Ned Burns9eb06332019-04-23 16:02:12 -0400506 int visibleIndex,
Evan Laird25f02752019-08-14 19:25:06 -0400507 View child,
508 View previousChild) {
509
510 boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
511 && visibleIndex > 0;
Ned Burns9eb06332019-04-23 16:02:12 -0400512 if (ANCHOR_SCROLLING) {
Selim Cinek54ef7be2020-05-27 19:09:21 -0700513 needsGapHeight &= visibleIndex != anchorViewIndex;
Ned Burns9eb06332019-04-23 16:02:12 -0400514 }
515 return needsGapHeight;
516 }
517
Muyuan Li87798022016-04-07 17:51:25 -0700518 protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
Selim Cinek61633a82016-01-25 15:54:10 -0800519 ExpandableView child) {
Selim Cinek281c2022016-10-13 19:14:43 -0700520 return algorithmState.getPaddingAfterChild(child);
Selim Cinek61633a82016-01-25 15:54:10 -0800521 }
522
Selim Cinek5040f2e2019-02-14 18:22:42 -0800523 private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
524 AmbientState ambientState) {
525 int childCount = algorithmState.visibleChildren.size();
526 for (int i = 0; i < childCount; i++) {
527 View child = algorithmState.visibleChildren.get(i);
528 if (!(child instanceof ExpandableNotificationRow)) {
529 continue;
530 }
531 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cinekc3fec682019-06-06 18:11:07 -0700532 if (!row.showingPulsing() || (i == 0 && ambientState.isPulseExpanding())) {
Selim Cinek5040f2e2019-02-14 18:22:42 -0800533 continue;
534 }
535 ExpandableViewState viewState = row.getViewState();
536 viewState.hidden = false;
537 }
538 }
539
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500540 private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
541 AmbientState ambientState) {
Selim Cineka4baaa32015-04-20 14:27:54 -0700542 int childCount = algorithmState.visibleChildren.size();
Steve Elliott0c904c32020-06-22 11:50:35 -0400543
544 // Move the tracked heads up into position during the appear animation, by interpolating
545 // between the HUN inset (where it will appear as a HUN) and the end position in the shade
546 ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
547 if (trackedHeadsUpRow != null) {
548 ExpandableViewState childState = trackedHeadsUpRow.getViewState();
549 if (childState != null) {
550 float endPosition = childState.yTranslation - ambientState.getStackTranslation();
551 childState.yTranslation = MathUtils.lerp(
552 mHeadsUpInset, endPosition, ambientState.getAppearFraction());
553 }
554 }
555
Selim Cineka4baaa32015-04-20 14:27:54 -0700556 ExpandableNotificationRow topHeadsUpEntry = null;
557 for (int i = 0; i < childCount; i++) {
558 View child = algorithmState.visibleChildren.get(i);
559 if (!(child instanceof ExpandableNotificationRow)) {
Selim Cineka3f6f882019-07-04 19:20:32 -0700560 continue;
Selim Cineka4baaa32015-04-20 14:27:54 -0700561 }
562 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
563 if (!row.isHeadsUp()) {
Selim Cineka3f6f882019-07-04 19:20:32 -0700564 continue;
Selim Cineka4baaa32015-04-20 14:27:54 -0700565 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500566 ExpandableViewState childState = row.getViewState();
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100567 if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
Selim Cinek3776fe02016-02-04 13:32:43 -0800568 topHeadsUpEntry = row;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800569 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
Selim Cinek3776fe02016-02-04 13:32:43 -0800570 }
Selim Cinek1f3f5442015-04-10 17:54:46 -0700571 boolean isTopEntry = topHeadsUpEntry == row;
Selim Cinek3776fe02016-02-04 13:32:43 -0800572 float unmodifiedEndLocation = childState.yTranslation + childState.height;
Selim Cinek131c1e22015-05-11 19:04:49 -0700573 if (mIsExpanded) {
Selim Cinekc3fec682019-06-06 18:11:07 -0700574 if (row.mustStayOnScreen() && !childState.headsUpIsVisible
575 && !row.showingPulsing()) {
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100576 // Ensure that the heads up is always visible even when scrolled off
577 clampHunToTop(ambientState, row, childState);
Steve Elliott0c904c32020-06-22 11:50:35 -0400578 if (isTopEntry && row.isAboveShelf()) {
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100579 // the first hun can't get off screen.
580 clampHunToMaxTranslation(ambientState, row, childState);
581 childState.hidden = false;
582 }
Selim Cinekd127d792016-11-01 19:11:41 -0700583 }
Selim Cinek131c1e22015-05-11 19:04:49 -0700584 }
Selim Cinek684a4422015-04-15 16:18:39 -0700585 if (row.isPinned()) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800586 childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset);
Selim Cinek31aada42015-12-18 17:51:15 -0800587 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
Selim Cinekcafa87f2016-10-26 17:00:17 -0700588 childState.hidden = false;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500589 ExpandableViewState topState =
590 topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
Selim Cinek61cfd4b2017-12-08 12:42:36 -0800591 if (topState != null && !isTopEntry && (!mIsExpanded
Selim Cineke3c6e462019-06-24 19:37:06 -0700592 || unmodifiedEndLocation > topState.yTranslation + topState.height)) {
Selim Cinek684a4422015-04-15 16:18:39 -0700593 // Ensure that a headsUp doesn't vertically extend further than the heads-up at
594 // the top most z-position
Selim Cinek31aada42015-12-18 17:51:15 -0800595 childState.height = row.getIntrinsicHeight();
Selim Cineke3c6e462019-06-24 19:37:06 -0700596 childState.yTranslation = Math.min(topState.yTranslation + topState.height
597 - childState.height, childState.yTranslation);
Selim Cinek1f3f5442015-04-10 17:54:46 -0700598 }
felkachang529bfe62018-07-04 12:51:44 +0800599
600 // heads up notification show and this row is the top entry of heads up
601 // notifications. i.e. this row should be the only one row that has input field
602 // To check if the row need to do translation according to scroll Y
603 // heads up show full of row's content and any scroll y indicate that the
604 // translationY need to move up the HUN.
Gus Prevas0fa58d62019-01-11 13:58:40 -0500605 // TODO: fix this check for anchor scrolling.
felkachang529bfe62018-07-04 12:51:44 +0800606 if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
607 childState.yTranslation -= ambientState.getScrollY();
608 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700609 }
Selim Cinekcafa87f2016-10-26 17:00:17 -0700610 if (row.isHeadsUpAnimatingAway()) {
611 childState.hidden = false;
612 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700613 }
Selim Cinek67b22602014-03-10 15:40:16 +0100614 }
615
Selim Cinek3776fe02016-02-04 13:32:43 -0800616 private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
Selim Cinekbbcebde2016-11-09 18:28:20 -0800617 ExpandableViewState childState) {
Selim Cinek3776fe02016-02-04 13:32:43 -0800618 float newTranslation = Math.max(ambientState.getTopPadding()
619 + ambientState.getStackTranslation(), childState.yTranslation);
620 childState.height = (int) Math.max(childState.height - (newTranslation
Selim Cinek567e8452016-03-24 10:54:56 -0700621 - childState.yTranslation), row.getCollapsedHeight());
Selim Cinek3776fe02016-02-04 13:32:43 -0800622 childState.yTranslation = newTranslation;
623 }
624
625 private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
Selim Cinekbbcebde2016-11-09 18:28:20 -0800626 ExpandableViewState childState) {
Selim Cinek3776fe02016-02-04 13:32:43 -0800627 float newTranslation;
Selim Cinek7e0f9482017-05-22 20:00:56 -0700628 float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
629 float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
630 + ambientState.getStackTranslation();
631 maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
632 float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
Selim Cinek3776fe02016-02-04 13:32:43 -0800633 newTranslation = Math.min(childState.yTranslation, bottomPosition);
Selim Cinek7e0f9482017-05-22 20:00:56 -0700634 childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
635 - newTranslation);
Selim Cinek3776fe02016-02-04 13:32:43 -0800636 childState.yTranslation = newTranslation;
Selim Cinek343e6e22014-04-11 21:23:30 +0200637 }
638
639 /**
Selim Cinek281c2022016-10-13 19:14:43 -0700640 * Clamp the height of the child down such that its end is at most on the beginning of
641 * the shelf.
642 *
Selim Cinek281c2022016-10-13 19:14:43 -0700643 * @param childViewState the view state of the child
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500644 * @param ambientState the ambient state
Selim Cinek281c2022016-10-13 19:14:43 -0700645 */
Selim Cinek2627d722018-01-19 12:16:49 -0800646 private void clampPositionToShelf(ExpandableView child,
647 ExpandableViewState childViewState,
Selim Cinek281c2022016-10-13 19:14:43 -0700648 AmbientState ambientState) {
Eliot Courtney5d3d2d02018-01-18 15:59:03 +0900649 if (ambientState.getShelf() == null) {
650 return;
651 }
652
Steve Elliott0c904c32020-06-22 11:50:35 -0400653 ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
654 boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
655 && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
656
Selim Cineka686b2c2016-10-26 13:58:27 -0700657 int shelfStart = ambientState.getInnerHeight()
658 - ambientState.getShelf().getIntrinsicHeight();
Steve Elliott0c904c32020-06-22 11:50:35 -0400659 if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
shawnlin5be1f7c2018-05-21 20:50:54 +0800660 // Don't show none heads-up notifications while in appearing phase.
661 childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
662 }
Selim Cinek281c2022016-10-13 19:14:43 -0700663 childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
Selim Cinekc383fd02016-10-21 15:31:26 -0700664 if (childViewState.yTranslation >= shelfStart) {
Selim Cinekc25989e2018-02-16 16:42:14 -0800665 childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700666 childViewState.inShelf = true;
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100667 childViewState.headsUpIsVisible = false;
Selim Cinekc383fd02016-10-21 15:31:26 -0700668 }
Selim Cinek281c2022016-10-13 19:14:43 -0700669 }
670
Muyuan Li87798022016-04-07 17:51:25 -0700671 protected int getMaxAllowedChildHeight(View child) {
Selim Cinek31aada42015-12-18 17:51:15 -0800672 if (child instanceof ExpandableView) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200673 ExpandableView expandableView = (ExpandableView) child;
Selim Cinek8d5727f2015-04-28 19:17:32 -0700674 return expandableView.getIntrinsicHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200675 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500676 return child == null ? mCollapsedSize : child.getHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200677 }
678
Selim Cinek67b22602014-03-10 15:40:16 +0100679 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100680 * Calculate the Z positions for all children based on the number of items in both stacks and
681 * save it in the resultState
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500682 *
Selim Cinek67b22602014-03-10 15:40:16 +0100683 * @param algorithmState The state in which the current pass of the algorithm is currently in
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500684 * @param ambientState The ambient state of the algorithm
Selim Cinek67b22602014-03-10 15:40:16 +0100685 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500686 private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
687 AmbientState ambientState) {
Jorim Jaggid4a57442014-04-10 02:45:55 +0200688 int childCount = algorithmState.visibleChildren.size();
Selim Cinek33223572016-02-19 19:32:22 -0800689 float childrenOnTop = 0.0f;
Selim Cinek3776fe02016-02-04 13:32:43 -0800690 for (int i = childCount - 1; i >= 0; i--) {
Selim Cinekdaab6f52017-04-06 16:46:34 -0700691 childrenOnTop = updateChildZValue(i, childrenOnTop,
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500692 algorithmState, ambientState);
Muyuan Li87798022016-04-07 17:51:25 -0700693 }
694 }
695
Selim Cinekdaab6f52017-04-06 16:46:34 -0700696 protected float updateChildZValue(int i, float childrenOnTop,
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500697 StackScrollAlgorithmState algorithmState,
Muyuan Li87798022016-04-07 17:51:25 -0700698 AmbientState ambientState) {
699 ExpandableView child = algorithmState.visibleChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500700 ExpandableViewState childViewState = child.getViewState();
Selim Cinek281c2022016-10-13 19:14:43 -0700701 int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
702 float baseZ = ambientState.getBaseZHeight();
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100703 if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
704 && !ambientState.isDozingAndNotPulsing(child)
Muyuan Li87798022016-04-07 17:51:25 -0700705 && childViewState.yTranslation < ambientState.getTopPadding()
706 + ambientState.getStackTranslation()) {
707 if (childrenOnTop != 0.0f) {
708 childrenOnTop++;
709 } else {
710 float overlap = ambientState.getTopPadding()
711 + ambientState.getStackTranslation() - childViewState.yTranslation;
712 childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
713 }
Selim Cinek281c2022016-10-13 19:14:43 -0700714 childViewState.zTranslation = baseZ
715 + childrenOnTop * zDistanceBetweenElements;
Steve Elliott0c904c32020-06-22 11:50:35 -0400716 } else if (child == ambientState.getTrackedHeadsUpRow()
717 || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) {
Selim Cinekd127d792016-11-01 19:11:41 -0700718 // In case this is a new view that has never been measured before, we don't want to
719 // elevate if we are currently expanded more then the notification
Eliot Courtney5d3d2d02018-01-18 15:59:03 +0900720 int shelfHeight = ambientState.getShelf() == null ? 0 :
721 ambientState.getShelf().getIntrinsicHeight();
Selim Cinekd127d792016-11-01 19:11:41 -0700722 float shelfStart = ambientState.getInnerHeight()
723 - shelfHeight + ambientState.getTopPadding()
724 + ambientState.getStackTranslation();
Steve Elliott0c904c32020-06-22 11:50:35 -0400725 float notificationEnd = childViewState.yTranslation + child.getIntrinsicHeight()
Selim Cinekd127d792016-11-01 19:11:41 -0700726 + mPaddingBetweenElements;
727 if (shelfStart > notificationEnd) {
728 childViewState.zTranslation = baseZ;
729 } else {
730 float factor = (notificationEnd - shelfStart) / shelfHeight;
731 factor = Math.min(factor, 1.0f);
732 childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
733 }
Muyuan Li87798022016-04-07 17:51:25 -0700734 } else {
Selim Cinek281c2022016-10-13 19:14:43 -0700735 childViewState.zTranslation = baseZ;
Selim Cinek67b22602014-03-10 15:40:16 +0100736 }
Selim Cinek99e9adf2018-03-15 09:17:47 -0700737
738 // We need to scrim the notification more from its surrounding content when we are pinned,
739 // and we therefore elevate it higher.
740 // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
741 // expanding after which we have a normal elevation again.
742 childViewState.zTranslation += (1.0f - child.getHeaderVisibleAmount())
743 * mPinnedZTranslationExtra;
Selim Cinekdaab6f52017-04-06 16:46:34 -0700744 return childrenOnTop;
Selim Cinek67b22602014-03-10 15:40:16 +0100745 }
746
Selim Cinek1685e632014-04-08 02:27:49 +0200747 public void setIsExpanded(boolean isExpanded) {
748 this.mIsExpanded = isExpanded;
749 }
750
Selim Cinek281c2022016-10-13 19:14:43 -0700751 public class StackScrollAlgorithmState {
Selim Cinek67b22602014-03-10 15:40:16 +0100752
753 /**
Gus Prevas0fa58d62019-01-11 13:58:40 -0500754 * The scroll position of the algorithm (absolute scrolling).
Selim Cinek67b22602014-03-10 15:40:16 +0100755 */
756 public int scrollY;
757
Gus Prevas0fa58d62019-01-11 13:58:40 -0500758 /** The index of the anchor view (anchor scrolling). */
759 public int anchorViewIndex;
760
761 /**
762 * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling).
763 */
764 public int anchorViewY;
765
Selim Cinek67b22602014-03-10 15:40:16 +0100766 /**
Jorim Jaggid4a57442014-04-10 02:45:55 +0200767 * The children from the host view which are not gone.
768 */
Jorim Jaggibe565df2014-04-28 17:51:23 +0200769 public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
Selim Cinek61633a82016-01-25 15:54:10 -0800770
771 /**
Selim Cineka7ed2c12017-01-23 20:47:24 -0800772 * The padding after each child measured in pixels.
Selim Cinek61633a82016-01-25 15:54:10 -0800773 */
Selim Cineka7ed2c12017-01-23 20:47:24 -0800774 public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
Selim Cinek2627d722018-01-19 12:16:49 -0800775 private int indexOfExpandingNotification;
Selim Cinek281c2022016-10-13 19:14:43 -0700776
777 public int getPaddingAfterChild(ExpandableView child) {
Selim Cineka7ed2c12017-01-23 20:47:24 -0800778 Float padding = paddingMap.get(child);
779 if (padding == null) {
780 // Should only happen for the last view
781 return mPaddingBetweenElements;
782 }
783 return (int) padding.floatValue();
Selim Cinek281c2022016-10-13 19:14:43 -0700784 }
Selim Cinek2627d722018-01-19 12:16:49 -0800785
786 public int getIndexOfExpandingNotification() {
787 return indexOfExpandingNotification;
788 }
Selim Cinek67b22602014-03-10 15:40:16 +0100789 }
790
Ned Burns9eb06332019-04-23 16:02:12 -0400791 /**
792 * Interface for telling the SSA when a new notification section begins (so it can add in
793 * appropriate margins).
794 */
795 public interface SectionProvider {
796 /**
797 * True if this view starts a new "section" of notifications, such as the gentle
798 * notifications section. False if sections are not enabled.
799 */
Evan Laird25f02752019-08-14 19:25:06 -0400800 boolean beginsSection(@NonNull View view, @Nullable View previous);
Ned Burns9eb06332019-04-23 16:02:12 -0400801 }
Selim Cinek67b22602014-03-10 15:40:16 +0100802}