1 /*
2  * Copyright (C) 2016 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;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.os.SystemProperties;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.accessibility.AccessibilityNodeInfo;
26 
27 import com.android.systemui.Interpolators;
28 import com.android.systemui.R;
29 import com.android.systemui.ViewInvertHelper;
30 import com.android.systemui.statusbar.notification.NotificationUtils;
31 import com.android.systemui.statusbar.phone.NotificationIconContainer;
32 import com.android.systemui.statusbar.phone.NotificationPanelView;
33 import com.android.systemui.statusbar.stack.AmbientState;
34 import com.android.systemui.statusbar.stack.AnimationProperties;
35 import com.android.systemui.statusbar.stack.ExpandableViewState;
36 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
37 import com.android.systemui.statusbar.stack.StackScrollState;
38 import com.android.systemui.statusbar.stack.ViewState;
39 
40 /**
41  * A notification shelf view that is placed inside the notification scroller. It manages the
42  * overflow icons that don't fit into the regular list anymore.
43  */
44 public class NotificationShelf extends ActivatableNotificationView implements
45         View.OnLayoutChangeListener {
46 
47     public static final boolean SHOW_AMBIENT_ICONS = true;
48     private static final boolean USE_ANIMATIONS_WHEN_OPENING =
49             SystemProperties.getBoolean("debug.icon_opening_animations", true);
50     private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
51             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
52     private ViewInvertHelper mViewInvertHelper;
53     private boolean mDark;
54     private NotificationIconContainer mShelfIcons;
55     private ShelfState mShelfState;
56     private int[] mTmp = new int[2];
57     private boolean mHideBackground;
58     private int mIconAppearTopPadding;
59     private int mStatusBarHeight;
60     private int mStatusBarPaddingStart;
61     private AmbientState mAmbientState;
62     private NotificationStackScrollLayout mHostLayout;
63     private int mMaxLayoutHeight;
64     private int mPaddingBetweenElements;
65     private int mNotGoneIndex;
66     private boolean mHasItemsInStableShelf;
67     private NotificationIconContainer mCollapsedIcons;
68     private int mScrollFastThreshold;
69     private int mStatusBarState;
70     private float mMaxShelfEnd;
71     private int mRelativeOffset;
72     private boolean mInteractive;
73     private float mOpenedAmount;
74     private boolean mNoAnimationsInThisFrame;
75     private boolean mAnimationsEnabled = true;
76 
NotificationShelf(Context context, AttributeSet attrs)77     public NotificationShelf(Context context, AttributeSet attrs) {
78         super(context, attrs);
79     }
80 
81     @Override
onFinishInflate()82     protected void onFinishInflate() {
83         super.onFinishInflate();
84         mShelfIcons = findViewById(R.id.content);
85         mShelfIcons.setClipChildren(false);
86         mShelfIcons.setClipToPadding(false);
87 
88         setClipToActualHeight(false);
89         setClipChildren(false);
90         setClipToPadding(false);
91         mShelfIcons.setShowAllIcons(false);
92         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
93                 NotificationPanelView.DOZE_ANIMATION_DURATION);
94         mShelfState = new ShelfState();
95         initDimens();
96     }
97 
bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout)98     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
99         mAmbientState = ambientState;
100         mHostLayout = hostLayout;
101     }
102 
initDimens()103     private void initDimens() {
104         mIconAppearTopPadding = getResources().getDimensionPixelSize(
105                 R.dimen.notification_icon_appear_padding);
106         mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
107         mStatusBarPaddingStart = getResources().getDimensionPixelOffset(
108                 R.dimen.status_bar_padding_start);
109         mPaddingBetweenElements = getResources().getDimensionPixelSize(
110                 R.dimen.notification_divider_height);
111         ViewGroup.LayoutParams layoutParams = getLayoutParams();
112         layoutParams.height = getResources().getDimensionPixelOffset(
113                 R.dimen.notification_shelf_height);
114         setLayoutParams(layoutParams);
115         int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
116         mShelfIcons.setPadding(padding, 0, padding, 0);
117         mScrollFastThreshold = getResources().getDimensionPixelOffset(
118                 R.dimen.scroll_fast_threshold);
119     }
120 
121     @Override
onConfigurationChanged(Configuration newConfig)122     protected void onConfigurationChanged(Configuration newConfig) {
123         super.onConfigurationChanged(newConfig);
124         initDimens();
125     }
126 
127     @Override
setDark(boolean dark, boolean fade, long delay)128     public void setDark(boolean dark, boolean fade, long delay) {
129         super.setDark(dark, fade, delay);
130         if (mDark == dark) return;
131         mDark = dark;
132         mShelfIcons.setDark(dark, fade, delay);
133         updateInteractiveness();
134     }
135 
136     @Override
getContentView()137     protected View getContentView() {
138         return mShelfIcons;
139     }
140 
getShelfIcons()141     public NotificationIconContainer getShelfIcons() {
142         return mShelfIcons;
143     }
144 
145     @Override
createNewViewState(StackScrollState stackScrollState)146     public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
147         return mShelfState;
148     }
149 
updateState(StackScrollState resultState, AmbientState ambientState)150     public void updateState(StackScrollState resultState,
151             AmbientState ambientState) {
152         View lastView = ambientState.getLastVisibleBackgroundChild();
153         if (lastView != null) {
154             float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
155                     + ambientState.getStackTranslation();
156             ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
157             float viewEnd = lastViewState.yTranslation + lastViewState.height;
158             mShelfState.copyFrom(lastViewState);
159             mShelfState.height = getIntrinsicHeight();
160             mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
161                     getFullyClosedTranslation());
162             mShelfState.zTranslation = ambientState.getBaseZHeight();
163             float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
164                     / (getIntrinsicHeight() * 2);
165             openedAmount = Math.min(1.0f, openedAmount);
166             mShelfState.openedAmount = openedAmount;
167             mShelfState.clipTopAmount = 0;
168             mShelfState.alpha = mAmbientState.hasPulsingNotifications() ? 0 : 1;
169             mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
170             mShelfState.shadowAlpha = 1.0f;
171             mShelfState.hideSensitive = false;
172             mShelfState.xTranslation = getTranslationX();
173             if (mNotGoneIndex != -1) {
174                 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
175             }
176             mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
177             mShelfState.hidden = !mAmbientState.isShadeExpanded();
178             mShelfState.maxShelfEnd = maxShelfEnd;
179         } else {
180             mShelfState.hidden = true;
181             mShelfState.location = ExpandableViewState.LOCATION_GONE;
182             mShelfState.hasItemsInStableShelf = false;
183         }
184     }
185 
186     /**
187      * Update the shelf appearance based on the other notifications around it. This transforms
188      * the icons from the notification area into the shelf.
189      */
updateAppearance()190     public void updateAppearance() {
191         mShelfIcons.resetViewStates();
192         float shelfStart = getTranslationY();
193         float numViewsInShelf = 0.0f;
194         View lastChild = mAmbientState.getLastVisibleBackgroundChild();
195         mNotGoneIndex = -1;
196         float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
197         float expandAmount = 0.0f;
198         if (shelfStart >= interpolationStart) {
199             expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
200             expandAmount = Math.min(1.0f, expandAmount);
201         }
202         //  find the first view that doesn't overlap with the shelf
203         int notificationIndex = 0;
204         int notGoneIndex = 0;
205         int colorOfViewBeforeLast = NO_COLOR;
206         boolean backgroundForceHidden = false;
207         if (mHideBackground && !mShelfState.hasItemsInStableShelf) {
208             backgroundForceHidden = true;
209         }
210         int colorTwoBefore = NO_COLOR;
211         int previousColor = NO_COLOR;
212         float transitionAmount = 0.0f;
213         float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
214         boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
215                 || (mAmbientState.isExpansionChanging()
216                         && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
217         boolean scrolling = currentScrollVelocity > 0;
218         boolean expandingAnimated = mAmbientState.isExpansionChanging()
219                 && !mAmbientState.isPanelTracking();
220         int baseZHeight = mAmbientState.getBaseZHeight();
221         while (notificationIndex < mHostLayout.getChildCount()) {
222             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
223             notificationIndex++;
224             if (!(child instanceof ExpandableNotificationRow)
225                     || child.getVisibility() == GONE) {
226                 continue;
227             }
228             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
229             float notificationClipEnd;
230             boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight;
231             boolean isLastChild = child == lastChild;
232             float rowTranslationY = row.getTranslationY();
233             if (isLastChild || aboveShelf || backgroundForceHidden) {
234                 notificationClipEnd = shelfStart + getIntrinsicHeight();
235             } else {
236                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
237                 float height = notificationClipEnd - rowTranslationY;
238                 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
239                     // We want the gap to close when we reached the minimum size and only shrink
240                     // before
241                     notificationClipEnd = Math.min(shelfStart,
242                             rowTranslationY + getNotificationMergeSize());
243                 }
244             }
245             updateNotificationClipHeight(row, notificationClipEnd);
246             float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast,
247                     expandingAnimated, isLastChild);
248             numViewsInShelf += inShelfAmount;
249             int ownColorUntinted = row.getBackgroundColorWithoutTint();
250             if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
251                 mNotGoneIndex = notGoneIndex;
252                 setTintColor(previousColor);
253                 setOverrideTintColor(colorTwoBefore, transitionAmount);
254 
255             } else if (mNotGoneIndex == -1) {
256                 colorTwoBefore = previousColor;
257                 transitionAmount = inShelfAmount;
258             }
259             if (isLastChild) {
260                 if (colorOfViewBeforeLast == NO_COLOR) {
261                     colorOfViewBeforeLast = ownColorUntinted;
262                 }
263                 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
264             } else {
265                 colorOfViewBeforeLast = ownColorUntinted;
266                 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
267             }
268             if (notGoneIndex != 0 || !aboveShelf) {
269                 row.setAboveShelf(false);
270             }
271             notGoneIndex++;
272             previousColor = ownColorUntinted;
273         }
274         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
275         mShelfIcons.calculateIconTranslations();
276         mShelfIcons.applyIconStates();
277         boolean hideBackground = numViewsInShelf < 1.0f;
278         setHideBackground(hideBackground || backgroundForceHidden);
279         if (mNotGoneIndex == -1) {
280             mNotGoneIndex = notGoneIndex;
281         }
282     }
283 
284     private void updateNotificationClipHeight(ExpandableNotificationRow row,
285             float notificationClipEnd) {
286         float viewEnd = row.getTranslationY() + row.getActualHeight();
287         boolean isPinned = row.isPinned() || row.isHeadsUpAnimatingAway();
288         if (viewEnd > notificationClipEnd
289                 && (mAmbientState.isShadeExpanded() || !isPinned)) {
290             int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
291             if (isPinned) {
292                 clipBottomAmount = Math.min(row.getIntrinsicHeight() - row.getCollapsedHeight(),
293                         clipBottomAmount);
294             }
295             row.setClipBottomAmount(clipBottomAmount);
296         } else {
297             row.setClipBottomAmount(0);
298         }
299     }
300 
301     /**
302      * @return the icon amount how much this notification is in the shelf;
303      */
304     private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
305             boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
306             boolean isLastChild) {
307         // Let calculate how much the view is in the shelf
308         float viewStart = row.getTranslationY();
309         int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
310         float iconTransformDistance = getIntrinsicHeight() * 1.5f;
311         iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
312         if (isLastChild) {
313             fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
314             iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
315                     - getIntrinsicHeight());
316         }
317         float viewEnd = viewStart + fullHeight;
318         float fullTransitionAmount;
319         float iconTransitionAmount;
320         float shelfStart = getTranslationY();
321         if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
322                 && (mAmbientState.isShadeExpanded()
323                         || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
324             if (viewStart < shelfStart) {
325 
326                 float fullAmount = (shelfStart - viewStart) / fullHeight;
327                 float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
328                         fullAmount);
329                 interpolatedAmount = NotificationUtils.interpolate(
330                         interpolatedAmount, fullAmount, expandAmount);
331                 fullTransitionAmount = 1.0f - interpolatedAmount;
332 
333                 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
334                 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
335                 iconTransitionAmount = 1.0f - iconTransitionAmount;
336 
337             } else {
338                 fullTransitionAmount = 1.0f;
339                 iconTransitionAmount = 1.0f;
340             }
341         } else {
342             fullTransitionAmount = 0.0f;
343             iconTransitionAmount = 0.0f;
344         }
345         updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
346                 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
347         return fullTransitionAmount;
348     }
349 
350     private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
351             float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
352             boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
353         StatusBarIconView icon = row.getEntry().expandedIcon;
354         NotificationIconContainer.IconState iconState = getIconState(icon);
355         if (iconState == null) {
356             return;
357         }
358         float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
359         if (clampedAmount == fullTransitionAmount) {
360             iconState.noAnimations = scrollingFast || expandingAnimated;
361             iconState.useFullTransitionAmount = iconState.noAnimations
362                 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
363             iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
364                     && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
365             iconState.translateContent = mMaxLayoutHeight - getTranslationY()
366                     - getIntrinsicHeight() > 0;
367         }
368         if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount
369                 && !ViewState.isAnimatingY(icon))) {
370             iconState.cancelAnimations(icon);
371             iconState.useFullTransitionAmount = true;
372             iconState.noAnimations = true;
373         }
374         float transitionAmount;
375         if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
376                 || iconState.useLinearTransitionAmount) {
377             transitionAmount = iconTransitionAmount;
378         } else {
379             // We take the clamped position instead
380             transitionAmount = clampedAmount;
381             iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount
382                     && !mNoAnimationsInThisFrame;
383         }
384         iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
385                     || iconState.useFullTransitionAmount
386                 ? fullTransitionAmount
387                 : transitionAmount;
388         iconState.clampedAppearAmount = clampedAmount;
389         float contentTransformationAmount = !row.isAboveShelf()
390                     && (isLastChild || iconState.translateContent)
391                 ? iconTransitionAmount
392                 : 0.0f;
393         row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
394         setIconTransformationAmount(row, transitionAmount, iconTransformDistance,
395                 clampedAmount != transitionAmount, isLastChild);
396     }
397 
398     private void setIconTransformationAmount(ExpandableNotificationRow row,
399             float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
400             boolean isLastChild) {
401         StatusBarIconView icon = row.getEntry().expandedIcon;
402         NotificationIconContainer.IconState iconState = getIconState(icon);
403 
404         View rowIcon = row.getNotificationIcon();
405         float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
406         boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
407         if (usingLinearInterpolation && !stayingInShelf) {
408             // If we interpolate from the notification position, this might lead to a slightly
409             // odd interpolation, since the notification position changes as well. Let's interpolate
410             // from a fixed distance. We can only do this if we don't animate and the icon is
411             // always in the interpolated positon.
412             notificationIconPosition = getTranslationY() - iconTransformDistance;
413         }
414         float notificationIconSize = 0.0f;
415         int iconTopPadding;
416         if (rowIcon != null) {
417             iconTopPadding = row.getRelativeTopPadding(rowIcon);
418             notificationIconSize = rowIcon.getHeight();
419         } else {
420             iconTopPadding = mIconAppearTopPadding;
421         }
422         notificationIconPosition += iconTopPadding;
423         float shelfIconPosition = getTranslationY() + icon.getTop();
424         shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
425         float iconYTranslation = NotificationUtils.interpolate(
426                 notificationIconPosition - shelfIconPosition,
427                 0,
428                 transitionAmount);
429         float shelfIconSize = icon.getHeight() * icon.getIconScale();
430         float alpha = 1.0f;
431         boolean noIcon = !row.isShowingIcon();
432         if (noIcon) {
433             // The view currently doesn't have an icon, lets transform it in!
434             alpha = transitionAmount;
435             notificationIconSize = shelfIconSize / 2.0f;
436         }
437         // The notification size is different from the size in the shelf / statusbar
438         float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
439                 transitionAmount);
440         if (iconState != null) {
441             iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
442             iconState.scaleY = iconState.scaleX;
443             iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
444             iconState.alpha = alpha;
445             iconState.yTranslation = iconYTranslation;
446             if (stayingInShelf) {
447                 iconState.iconAppearAmount = 1.0f;
448                 iconState.alpha = 1.0f;
449                 iconState.scaleX = 1.0f;
450                 iconState.scaleY = 1.0f;
451                 iconState.hidden = false;
452             }
453             if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
454                     || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
455                 iconState.hidden = true;
456             }
457             int backgroundColor = getBackgroundColorWithoutTint();
458             int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
459             if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
460                 int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
461                 shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
462                         iconState.iconAppearAmount);
463             }
464             iconState.iconColor = shelfColor;
465         }
466     }
467 
468     private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
469         return mShelfIcons.getIconState(icon);
470     }
471 
472     private float getFullyClosedTranslation() {
473         return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
474     }
475 
476     public int getNotificationMergeSize() {
477         return getIntrinsicHeight();
478     }
479 
480     @Override
481     public boolean hasNoContentHeight() {
482         return true;
483     }
484 
485     private void setHideBackground(boolean hideBackground) {
486         if (mHideBackground != hideBackground) {
487             mHideBackground = hideBackground;
488             updateBackground();
489             updateOutline();
490         }
491     }
492 
493     public boolean hidesBackground() {
494         return mHideBackground;
495     }
496 
497     @Override
498     protected boolean needsOutline() {
499         return !mHideBackground && super.needsOutline();
500     }
501 
502     @Override
503     protected boolean shouldHideBackground() {
504         return super.shouldHideBackground() || mHideBackground;
505     }
506 
507     @Override
508     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
509         super.onLayout(changed, left, top, right, bottom);
510         updateRelativeOffset();
511     }
512 
513     private void updateRelativeOffset() {
514         mCollapsedIcons.getLocationOnScreen(mTmp);
515         mRelativeOffset = mTmp[0];
516         getLocationOnScreen(mTmp);
517         mRelativeOffset -= mTmp[0];
518     }
519 
520     private void setOpenedAmount(float openedAmount) {
521         mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f;
522         mOpenedAmount = openedAmount;
523         if (!mAmbientState.isPanelFullWidth()) {
524             // We don't do a transformation at all, lets just assume we are fully opened
525             openedAmount = 1.0f;
526         }
527         int start = mRelativeOffset;
528         if (isLayoutRtl()) {
529             start = getWidth() - start - mCollapsedIcons.getWidth();
530         }
531         int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
532                 mShelfIcons.getWidth(),
533                 openedAmount);
534         mShelfIcons.setActualLayoutWidth(width);
535         boolean hasOverflow = mCollapsedIcons.hasOverflow();
536         int collapsedPadding = mCollapsedIcons.getPaddingEnd();
537         if (!hasOverflow) {
538             // we have to ensure that adding the low priority notification won't lead to an
539             // overflow
540             collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
541                     * mCollapsedIcons.getIconSize();
542         }
543         float padding = NotificationUtils.interpolate(collapsedPadding,
544                 mShelfIcons.getPaddingEnd(),
545                 openedAmount);
546         mShelfIcons.setActualPaddingEnd(padding);
547         float paddingStart = NotificationUtils.interpolate(start,
548                 mShelfIcons.getPaddingStart(), openedAmount);
549         mShelfIcons.setActualPaddingStart(paddingStart);
550         mShelfIcons.setOpenedAmount(openedAmount);
551         mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
552     }
553 
554     public void setMaxLayoutHeight(int maxLayoutHeight) {
555         mMaxLayoutHeight = maxLayoutHeight;
556     }
557 
558     /**
559      * @return the index of the notification at which the shelf visually resides
560      */
561     public int getNotGoneIndex() {
562         return mNotGoneIndex;
563     }
564 
565     private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
566         if (mHasItemsInStableShelf != hasItemsInStableShelf) {
567             mHasItemsInStableShelf = hasItemsInStableShelf;
568             updateInteractiveness();
569         }
570     }
571 
572     /**
573      * @return whether the shelf has any icons in it when a potential animation has finished, i.e
574      *         if the current state would be applied right now
575      */
576     public boolean hasItemsInStableShelf() {
577         return mHasItemsInStableShelf;
578     }
579 
580     public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
581         mCollapsedIcons = collapsedIcons;
582         mCollapsedIcons.addOnLayoutChangeListener(this);
583     }
584 
585     public void setStatusBarState(int statusBarState) {
586         if (mStatusBarState != statusBarState) {
587             mStatusBarState = statusBarState;
588             updateInteractiveness();
589         }
590     }
591 
592     private void updateInteractiveness() {
593         mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf
594                 && !mDark;
595         setClickable(mInteractive);
596         setFocusable(mInteractive);
597         setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
598                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
599     }
600 
601     @Override
602     protected boolean isInteractive() {
603         return mInteractive;
604     }
605 
606     public void setMaxShelfEnd(float maxShelfEnd) {
607         mMaxShelfEnd = maxShelfEnd;
608     }
609 
610     public void setAnimationsEnabled(boolean enabled) {
611         mAnimationsEnabled = enabled;
612         mCollapsedIcons.setAnimationsEnabled(enabled);
613         if (!enabled) {
614             // we need to wait with enabling the animations until the first frame has passed
615             mShelfIcons.setAnimationsEnabled(false);
616         }
617     }
618 
619     @Override
620     public boolean hasOverlappingRendering() {
621         return false;  // Shelf only uses alpha for transitions where the difference can't be seen.
622     }
623 
624     @Override
625     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
626         super.onInitializeAccessibilityNodeInfo(info);
627         if (mInteractive) {
628             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
629             AccessibilityNodeInfo.AccessibilityAction unlock
630                     = new AccessibilityNodeInfo.AccessibilityAction(
631                     AccessibilityNodeInfo.ACTION_CLICK,
632                     getContext().getString(R.string.accessibility_overflow_action));
633             info.addAction(unlock);
634         }
635     }
636 
637     @Override
638     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
639             int oldTop, int oldRight, int oldBottom) {
640         updateRelativeOffset();
641     }
642 
643     private class ShelfState extends ExpandableViewState {
644         private float openedAmount;
645         private boolean hasItemsInStableShelf;
646         private float maxShelfEnd;
647 
648         @Override
649         public void applyToView(View view) {
650             super.applyToView(view);
651             setMaxShelfEnd(maxShelfEnd);
652             setOpenedAmount(openedAmount);
653             updateAppearance();
654             setHasItemsInStableShelf(hasItemsInStableShelf);
655             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
656         }
657 
658         @Override
659         public void animateTo(View child, AnimationProperties properties) {
660             super.animateTo(child, properties);
661             setMaxShelfEnd(maxShelfEnd);
662             setOpenedAmount(openedAmount);
663             updateAppearance();
664             setHasItemsInStableShelf(hasItemsInStableShelf);
665             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
666         }
667     }
668 }
669