• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.stack;
18 
19 import android.content.Context;
20 import android.util.Log;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 import com.android.systemui.R;
25 import com.android.systemui.statusbar.DismissView;
26 import com.android.systemui.statusbar.EmptyShadeView;
27 import com.android.systemui.statusbar.ExpandableNotificationRow;
28 import com.android.systemui.statusbar.ExpandableView;
29 import com.android.systemui.statusbar.NotificationShelf;
30 import com.android.systemui.statusbar.notification.NotificationUtils;
31 
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 
36 /**
37  * The Algorithm of the {@link com.android.systemui.statusbar.stack
38  * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
39  * .stack.StackScrollState}
40  */
41 public class StackScrollAlgorithm {
42 
43     private static final String LOG_TAG = "StackScrollAlgorithm";
44 
45     private int mPaddingBetweenElements;
46     private int mIncreasedPaddingBetweenElements;
47     private int mCollapsedSize;
48 
49     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
50     private boolean mIsExpanded;
51     private int mStatusBarHeight;
52 
StackScrollAlgorithm(Context context)53     public StackScrollAlgorithm(Context context) {
54         initView(context);
55     }
56 
initView(Context context)57     public void initView(Context context) {
58         initConstants(context);
59     }
60 
initConstants(Context context)61     private void initConstants(Context context) {
62         mPaddingBetweenElements = context.getResources().getDimensionPixelSize(
63                 R.dimen.notification_divider_height);
64         mIncreasedPaddingBetweenElements = context.getResources()
65                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
66         mCollapsedSize = context.getResources()
67                 .getDimensionPixelSize(R.dimen.notification_min_height);
68         mStatusBarHeight = context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
69     }
70 
getStackScrollState(AmbientState ambientState, StackScrollState resultState)71     public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
72         // The state of the local variables are saved in an algorithmState to easily subdivide it
73         // into multiple phases.
74         StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
75 
76         // First we reset the view states to their default values.
77         resultState.resetViewStates();
78 
79         initAlgorithmState(resultState, algorithmState, ambientState);
80 
81         updatePositionsForState(resultState, algorithmState, ambientState);
82 
83         updateZValuesForState(resultState, algorithmState, ambientState);
84 
85         updateHeadsUpStates(resultState, algorithmState, ambientState);
86 
87         handleDraggedViews(ambientState, resultState, algorithmState);
88         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
89         updateClipping(resultState, algorithmState, ambientState);
90         updateSpeedBumpState(resultState, algorithmState, ambientState);
91         updateShelfState(resultState, ambientState);
92         getNotificationChildrenStates(resultState, algorithmState);
93     }
94 
getNotificationChildrenStates(StackScrollState resultState, StackScrollAlgorithmState algorithmState)95     private void getNotificationChildrenStates(StackScrollState resultState,
96             StackScrollAlgorithmState algorithmState) {
97         int childCount = algorithmState.visibleChildren.size();
98         for (int i = 0; i < childCount; i++) {
99             ExpandableView v = algorithmState.visibleChildren.get(i);
100             if (v instanceof ExpandableNotificationRow) {
101                 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
102                 row.getChildrenStates(resultState);
103             }
104         }
105     }
106 
updateSpeedBumpState(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)107     private void updateSpeedBumpState(StackScrollState resultState,
108             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
109         int childCount = algorithmState.visibleChildren.size();
110         int belowSpeedBump = ambientState.getSpeedBumpIndex();
111         for (int i = 0; i < childCount; i++) {
112             View child = algorithmState.visibleChildren.get(i);
113             ExpandableViewState childViewState = resultState.getViewStateForView(child);
114 
115             // The speed bump can also be gone, so equality needs to be taken when comparing
116             // indices.
117             childViewState.belowSpeedBump = i >= belowSpeedBump;
118         }
119 
120     }
updateShelfState(StackScrollState resultState, AmbientState ambientState)121     private void updateShelfState(StackScrollState resultState, AmbientState ambientState) {
122         NotificationShelf shelf = ambientState.getShelf();
123         shelf.updateState(resultState, ambientState);
124     }
125 
updateClipping(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)126     private void updateClipping(StackScrollState resultState,
127             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
128         float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
129                 + ambientState.getStackTranslation() : 0;
130         float previousNotificationEnd = 0;
131         float previousNotificationStart = 0;
132         int childCount = algorithmState.visibleChildren.size();
133         for (int i = 0; i < childCount; i++) {
134             ExpandableView child = algorithmState.visibleChildren.get(i);
135             ExpandableViewState state = resultState.getViewStateForView(child);
136             if (!child.mustStayOnScreen()) {
137                 previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
138                 previousNotificationStart = Math.max(drawStart, previousNotificationStart);
139             }
140             float newYTranslation = state.yTranslation;
141             float newHeight = state.height;
142             float newNotificationEnd = newYTranslation + newHeight;
143             boolean isHeadsUp = (child instanceof ExpandableNotificationRow)
144                     && ((ExpandableNotificationRow) child).isPinned();
145             if (!state.inShelf && newYTranslation < previousNotificationEnd
146                     && (!isHeadsUp || ambientState.isShadeExpanded())) {
147                 // The previous view is overlapping on top, clip!
148                 float overlapAmount = previousNotificationEnd - newYTranslation;
149                 state.clipTopAmount = (int) overlapAmount;
150             } else {
151                 state.clipTopAmount = 0;
152             }
153 
154             if (!child.isTransparent()) {
155                 // Only update the previous values if we are not transparent,
156                 // otherwise we would clip to a transparent view.
157                 previousNotificationEnd = newNotificationEnd;
158                 previousNotificationStart = newYTranslation;
159             }
160         }
161     }
162 
canChildBeDismissed(View v)163     public static boolean canChildBeDismissed(View v) {
164         if (!(v instanceof ExpandableNotificationRow)) {
165             return false;
166         }
167         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
168         if (row.areGutsExposed()) {
169             return false;
170         }
171         return row.canViewBeDismissed();
172     }
173 
174     /**
175      * Updates the dimmed, activated and hiding sensitive states of the children.
176      */
updateDimmedActivatedHideSensitive(AmbientState ambientState, StackScrollState resultState, StackScrollAlgorithmState algorithmState)177     private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
178             StackScrollState resultState, StackScrollAlgorithmState algorithmState) {
179         boolean dimmed = ambientState.isDimmed();
180         boolean dark = ambientState.isDark();
181         boolean hideSensitive = ambientState.isHideSensitive();
182         View activatedChild = ambientState.getActivatedChild();
183         int childCount = algorithmState.visibleChildren.size();
184         for (int i = 0; i < childCount; i++) {
185             View child = algorithmState.visibleChildren.get(i);
186             ExpandableViewState childViewState = resultState.getViewStateForView(child);
187             childViewState.dimmed = dimmed;
188             childViewState.dark = dark;
189             childViewState.hideSensitive = hideSensitive;
190             boolean isActivatedChild = activatedChild == child;
191             if (dimmed && isActivatedChild) {
192                 childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
193             }
194         }
195     }
196 
197     /**
198      * Handle the special state when views are being dragged
199      */
handleDraggedViews(AmbientState ambientState, StackScrollState resultState, StackScrollAlgorithmState algorithmState)200     private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState,
201             StackScrollAlgorithmState algorithmState) {
202         ArrayList<View> draggedViews = ambientState.getDraggedViews();
203         for (View draggedView : draggedViews) {
204             int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
205             if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
206                 View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
207                 if (!draggedViews.contains(nextChild)) {
208                     // only if the view is not dragged itself we modify its state to be fully
209                     // visible
210                     ExpandableViewState viewState = resultState.getViewStateForView(
211                             nextChild);
212                     // The child below the dragged one must be fully visible
213                     if (ambientState.isShadeExpanded()) {
214                         viewState.shadowAlpha = 1;
215                         viewState.hidden = false;
216                     }
217                 }
218 
219                 // Lets set the alpha to the one it currently has, as its currently being dragged
220                 ExpandableViewState viewState = resultState.getViewStateForView(draggedView);
221                 // The dragged child should keep the set alpha
222                 viewState.alpha = draggedView.getAlpha();
223             }
224         }
225     }
226 
227     /**
228      * Initialize the algorithm state like updating the visible children.
229      */
initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state, AmbientState ambientState)230     private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state,
231             AmbientState ambientState) {
232         float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
233 
234         int scrollY = ambientState.getScrollY();
235 
236         // Due to the overScroller, the stackscroller can have negative scroll state. This is
237         // already accounted for by the top padding and doesn't need an additional adaption
238         scrollY = Math.max(0, scrollY);
239         state.scrollY = (int) (scrollY + bottomOverScroll);
240 
241         //now init the visible children and update paddings
242         ViewGroup hostView = resultState.getHostView();
243         int childCount = hostView.getChildCount();
244         state.visibleChildren.clear();
245         state.visibleChildren.ensureCapacity(childCount);
246         state.paddingMap.clear();
247         int notGoneIndex = 0;
248         ExpandableView lastView = null;
249         for (int i = 0; i < childCount; i++) {
250             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
251             if (v.getVisibility() != View.GONE) {
252                 if (v == ambientState.getShelf()) {
253                     continue;
254                 }
255                 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
256                 float increasedPadding = v.getIncreasedPaddingAmount();;
257                 if (increasedPadding != 0.0f) {
258                     state.paddingMap.put(v, increasedPadding);
259                     if (lastView != null) {
260                         Float prevValue = state.paddingMap.get(lastView);
261                         float newValue = getPaddingForValue(increasedPadding);
262                         if (prevValue != null) {
263                             float prevPadding = getPaddingForValue(prevValue);
264                             if (increasedPadding > 0) {
265                                 newValue = NotificationUtils.interpolate(
266                                         prevPadding,
267                                         newValue,
268                                         increasedPadding);
269                             } else if (prevValue > 0) {
270                                 newValue = NotificationUtils.interpolate(
271                                         newValue,
272                                         prevPadding,
273                                         prevValue);
274                             }
275                         }
276                         state.paddingMap.put(lastView, newValue);
277                     }
278                 } else if (lastView != null) {
279                     float newValue = getPaddingForValue(state.paddingMap.get(lastView));
280                     state.paddingMap.put(lastView, newValue);
281                 }
282                 if (v instanceof ExpandableNotificationRow) {
283                     ExpandableNotificationRow row = (ExpandableNotificationRow) v;
284 
285                     // handle the notgoneIndex for the children as well
286                     List<ExpandableNotificationRow> children =
287                             row.getNotificationChildren();
288                     if (row.isSummaryWithChildren() && children != null) {
289                         for (ExpandableNotificationRow childRow : children) {
290                             if (childRow.getVisibility() != View.GONE) {
291                                 ExpandableViewState childState
292                                         = resultState.getViewStateForView(childRow);
293                                 childState.notGoneIndex = notGoneIndex;
294                                 notGoneIndex++;
295                             }
296                         }
297                     }
298                 }
299                 lastView = v;
300             }
301         }
302     }
303 
getPaddingForValue(Float increasedPadding)304     private float getPaddingForValue(Float increasedPadding) {
305         if (increasedPadding == null) {
306             return mPaddingBetweenElements;
307         } else if (increasedPadding >= 0.0f) {
308             return NotificationUtils.interpolate(
309                     mPaddingBetweenElements,
310                     mIncreasedPaddingBetweenElements,
311                     increasedPadding);
312         } else {
313             return NotificationUtils.interpolate(
314                     0,
315                     mPaddingBetweenElements,
316                     1.0f + increasedPadding);
317         }
318     }
319 
updateNotGoneIndex(StackScrollState resultState, StackScrollAlgorithmState state, int notGoneIndex, ExpandableView v)320     private int updateNotGoneIndex(StackScrollState resultState,
321             StackScrollAlgorithmState state, int notGoneIndex,
322             ExpandableView v) {
323         ExpandableViewState viewState = resultState.getViewStateForView(v);
324         viewState.notGoneIndex = notGoneIndex;
325         state.visibleChildren.add(v);
326         notGoneIndex++;
327         return notGoneIndex;
328     }
329 
330     /**
331      * Determine the positions for the views. This is the main part of the algorithm.
332      *
333      * @param resultState The result state to update if a change to the properties of a child occurs
334      * @param algorithmState The state in which the current pass of the algorithm is currently in
335      * @param ambientState The current ambient state
336      */
updatePositionsForState(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)337     private void updatePositionsForState(StackScrollState resultState,
338             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
339 
340         // The y coordinate of the current child.
341         float currentYPosition = -algorithmState.scrollY;
342         int childCount = algorithmState.visibleChildren.size();
343         for (int i = 0; i < childCount; i++) {
344             currentYPosition = updateChild(i, resultState, algorithmState, ambientState,
345                     currentYPosition);
346         }
347     }
348 
updateChild(int i, StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState, float currentYPosition)349     protected float updateChild(int i, StackScrollState resultState,
350             StackScrollAlgorithmState algorithmState, AmbientState ambientState,
351             float currentYPosition) {
352         ExpandableView child = algorithmState.visibleChildren.get(i);
353         ExpandableViewState childViewState = resultState.getViewStateForView(child);
354         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
355         int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
356         int childHeight = getMaxAllowedChildHeight(child);
357         childViewState.yTranslation = currentYPosition;
358         boolean isDismissView = child instanceof DismissView;
359         boolean isEmptyShadeView = child instanceof EmptyShadeView;
360 
361         childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
362         if (isDismissView) {
363             childViewState.yTranslation = Math.min(childViewState.yTranslation,
364                     ambientState.getInnerHeight() - childHeight);
365         } else if (isEmptyShadeView) {
366             childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
367                     + ambientState.getStackTranslation() * 0.25f;
368         } else {
369             clampPositionToShelf(childViewState, ambientState);
370         }
371 
372         currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
373         if (currentYPosition <= 0) {
374             childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
375         }
376         if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
377             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
378         }
379 
380         childViewState.yTranslation += ambientState.getTopPadding()
381                 + ambientState.getStackTranslation();
382         return currentYPosition;
383     }
384 
getPaddingAfterChild(StackScrollAlgorithmState algorithmState, ExpandableView child)385     protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
386             ExpandableView child) {
387         return algorithmState.getPaddingAfterChild(child);
388     }
389 
updateHeadsUpStates(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)390     private void updateHeadsUpStates(StackScrollState resultState,
391             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
392         int childCount = algorithmState.visibleChildren.size();
393         ExpandableNotificationRow topHeadsUpEntry = null;
394         for (int i = 0; i < childCount; i++) {
395             View child = algorithmState.visibleChildren.get(i);
396             if (!(child instanceof ExpandableNotificationRow)) {
397                 break;
398             }
399             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
400             if (!row.isHeadsUp()) {
401                 break;
402             }
403             ExpandableViewState childState = resultState.getViewStateForView(row);
404             if (topHeadsUpEntry == null) {
405                 topHeadsUpEntry = row;
406                 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
407             }
408             boolean isTopEntry = topHeadsUpEntry == row;
409             float unmodifiedEndLocation = childState.yTranslation + childState.height;
410             if (mIsExpanded) {
411                 // Ensure that the heads up is always visible even when scrolled off
412                 clampHunToTop(ambientState, row, childState);
413                 if (i == 0 && row.isAboveShelf()) {
414                     // the first hun can't get off screen.
415                     clampHunToMaxTranslation(ambientState, row, childState);
416                     childState.hidden = false;
417                 }
418             }
419             if (row.isPinned()) {
420                 childState.yTranslation = Math.max(childState.yTranslation, 0);
421                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
422                 childState.hidden = false;
423                 ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
424                 if (!isTopEntry && (!mIsExpanded
425                         || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
426                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
427                     // the top most z-position
428                     childState.height = row.getIntrinsicHeight();
429                     childState.yTranslation = topState.yTranslation + topState.height
430                             - childState.height;
431                 }
432             }
433             if (row.isHeadsUpAnimatingAway()) {
434                 childState.hidden = false;
435             }
436         }
437     }
438 
clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row, ExpandableViewState childState)439     private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
440             ExpandableViewState childState) {
441         float newTranslation = Math.max(ambientState.getTopPadding()
442                 + ambientState.getStackTranslation(), childState.yTranslation);
443         childState.height = (int) Math.max(childState.height - (newTranslation
444                 - childState.yTranslation), row.getCollapsedHeight());
445         childState.yTranslation = newTranslation;
446     }
447 
clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row, ExpandableViewState childState)448     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
449             ExpandableViewState childState) {
450         float newTranslation;
451         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
452         float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
453                 + ambientState.getStackTranslation();
454         maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
455         float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
456         newTranslation = Math.min(childState.yTranslation, bottomPosition);
457         childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
458                 - newTranslation);
459         childState.yTranslation = newTranslation;
460     }
461 
462     /**
463      * Clamp the height of the child down such that its end is at most on the beginning of
464      * the shelf.
465      *
466      * @param childViewState the view state of the child
467      * @param ambientState the ambient state
468      */
clampPositionToShelf(ExpandableViewState childViewState, AmbientState ambientState)469     private void clampPositionToShelf(ExpandableViewState childViewState,
470             AmbientState ambientState) {
471         int shelfStart = ambientState.getInnerHeight()
472                 - ambientState.getShelf().getIntrinsicHeight();
473         childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
474         if (childViewState.yTranslation >= shelfStart) {
475             childViewState.hidden = true;
476             childViewState.inShelf = true;
477         }
478         if (!ambientState.isShadeExpanded()) {
479             childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation);
480         }
481     }
482 
getMaxAllowedChildHeight(View child)483     protected int getMaxAllowedChildHeight(View child) {
484         if (child instanceof ExpandableView) {
485             ExpandableView expandableView = (ExpandableView) child;
486             return expandableView.getIntrinsicHeight();
487         }
488         return child == null? mCollapsedSize : child.getHeight();
489     }
490 
491     /**
492      * Calculate the Z positions for all children based on the number of items in both stacks and
493      * save it in the resultState
494      *  @param resultState The result state to update the zTranslation values
495      * @param algorithmState The state in which the current pass of the algorithm is currently in
496      * @param ambientState The ambient state of the algorithm
497      */
updateZValuesForState(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)498     private void updateZValuesForState(StackScrollState resultState,
499             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
500         int childCount = algorithmState.visibleChildren.size();
501         float childrenOnTop = 0.0f;
502         for (int i = childCount - 1; i >= 0; i--) {
503             childrenOnTop = updateChildZValue(i, childrenOnTop,
504                     resultState, algorithmState, ambientState);
505         }
506     }
507 
updateChildZValue(int i, float childrenOnTop, StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState)508     protected float updateChildZValue(int i, float childrenOnTop,
509             StackScrollState resultState, StackScrollAlgorithmState algorithmState,
510             AmbientState ambientState) {
511         ExpandableView child = algorithmState.visibleChildren.get(i);
512         ExpandableViewState childViewState = resultState.getViewStateForView(child);
513         int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
514         float baseZ = ambientState.getBaseZHeight();
515         if (child.mustStayOnScreen()
516                 && childViewState.yTranslation < ambientState.getTopPadding()
517                 + ambientState.getStackTranslation()) {
518             if (childrenOnTop != 0.0f) {
519                 childrenOnTop++;
520             } else {
521                 float overlap = ambientState.getTopPadding()
522                         + ambientState.getStackTranslation() - childViewState.yTranslation;
523                 childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
524             }
525             childViewState.zTranslation = baseZ
526                     + childrenOnTop * zDistanceBetweenElements;
527         } else if (i == 0 && child.isAboveShelf()) {
528             // In case this is a new view that has never been measured before, we don't want to
529             // elevate if we are currently expanded more then the notification
530             int shelfHeight = ambientState.getShelf().getIntrinsicHeight();
531             float shelfStart = ambientState.getInnerHeight()
532                     - shelfHeight + ambientState.getTopPadding()
533                     + ambientState.getStackTranslation();
534             float notificationEnd = childViewState.yTranslation + child.getPinnedHeadsUpHeight()
535                     + mPaddingBetweenElements;
536             if (shelfStart > notificationEnd) {
537                 childViewState.zTranslation = baseZ;
538             } else {
539                 float factor = (notificationEnd - shelfStart) / shelfHeight;
540                 factor = Math.min(factor, 1.0f);
541                 childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
542             }
543         } else {
544             childViewState.zTranslation = baseZ;
545         }
546         return childrenOnTop;
547     }
548 
setIsExpanded(boolean isExpanded)549     public void setIsExpanded(boolean isExpanded) {
550         this.mIsExpanded = isExpanded;
551     }
552 
553     public class StackScrollAlgorithmState {
554 
555         /**
556          * The scroll position of the algorithm
557          */
558         public int scrollY;
559 
560         /**
561          * The children from the host view which are not gone.
562          */
563         public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
564 
565         /**
566          * The padding after each child measured in pixels.
567          */
568         public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
569 
getPaddingAfterChild(ExpandableView child)570         public int getPaddingAfterChild(ExpandableView child) {
571             Float padding = paddingMap.get(child);
572             if (padding == null) {
573                 // Should only happen for the last view
574                 return mPaddingBetweenElements;
575             }
576             return (int) padding.floatValue();
577         }
578     }
579 
580 }
581