Home | History | Annotate | Download | only in statusbar
      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 
     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