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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.os.SystemProperties;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.accessibility.AccessibilityNodeInfo;
26
27import com.android.systemui.Interpolators;
28import com.android.systemui.R;
29import com.android.systemui.ViewInvertHelper;
30import com.android.systemui.statusbar.notification.NotificationUtils;
31import com.android.systemui.statusbar.phone.NotificationIconContainer;
32import com.android.systemui.statusbar.phone.NotificationPanelView;
33import com.android.systemui.statusbar.stack.AmbientState;
34import com.android.systemui.statusbar.stack.AnimationProperties;
35import com.android.systemui.statusbar.stack.ExpandableViewState;
36import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
37import com.android.systemui.statusbar.stack.StackScrollState;
38import 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 */
44public 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
77    public NotificationShelf(Context context, AttributeSet attrs) {
78        super(context, attrs);
79    }
80
81    @Override
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
98    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
99        mAmbientState = ambientState;
100        mHostLayout = hostLayout;
101    }
102
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
122    protected void onConfigurationChanged(Configuration newConfig) {
123        super.onConfigurationChanged(newConfig);
124        initDimens();
125    }
126
127    @Override
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
137    protected View getContentView() {
138        return mShelfIcons;
139    }
140
141    public NotificationIconContainer getShelfIcons() {
142        return mShelfIcons;
143    }
144
145    @Override
146    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
147        return mShelfState;
148    }
149
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     */
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