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