Home | History | Annotate | Download | only in phone
      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.phone;
     18 
     19 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION;
     20 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
     21 
     22 import android.content.Context;
     23 import android.content.res.Configuration;
     24 import android.graphics.Canvas;
     25 import android.graphics.Color;
     26 import android.graphics.Paint;
     27 import android.graphics.Rect;
     28 import android.graphics.drawable.Icon;
     29 import android.support.v4.util.ArrayMap;
     30 import android.util.AttributeSet;
     31 import android.view.View;
     32 
     33 import com.android.internal.statusbar.StatusBarIcon;
     34 import com.android.systemui.Interpolators;
     35 import com.android.systemui.R;
     36 import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
     37 import com.android.systemui.statusbar.StatusBarIconView;
     38 import com.android.systemui.statusbar.stack.AnimationFilter;
     39 import com.android.systemui.statusbar.stack.AnimationProperties;
     40 import com.android.systemui.statusbar.stack.ViewState;
     41 
     42 import java.util.ArrayList;
     43 import java.util.HashMap;
     44 
     45 /**
     46  * A container for notification icons. It handles overflowing icons properly and positions them
     47  * correctly on the screen.
     48  */
     49 public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
     50     /**
     51      * A float value indicating how much before the overflow start the icons should transform into
     52      * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
     53      * 1 icon width early.
     54      */
     55     public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
     56     private static final int NO_VALUE = Integer.MIN_VALUE;
     57     private static final String TAG = "NotificationIconContainer";
     58     private static final boolean DEBUG = false;
     59     private static final boolean DEBUG_OVERFLOW = false;
     60     private static final int CANNED_ANIMATION_DURATION = 100;
     61     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
     62         private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
     63 
     64         @Override
     65         public AnimationFilter getAnimationFilter() {
     66             return mAnimationFilter;
     67         }
     68     }.setDuration(200);
     69 
     70     private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
     71         private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha()
     72                 .animateScale();
     73 
     74         @Override
     75         public AnimationFilter getAnimationFilter() {
     76             return mAnimationFilter;
     77         }
     78 
     79     }.setDuration(CANNED_ANIMATION_DURATION)
     80             .setCustomInterpolator(View.TRANSLATION_Y, Interpolators.ICON_OVERSHOT);
     81 
     82     /**
     83      * Temporary AnimationProperties to avoid unnecessary allocations.
     84      */
     85     private static final AnimationProperties sTempProperties = new AnimationProperties() {
     86         private AnimationFilter mAnimationFilter = new AnimationFilter();
     87 
     88         @Override
     89         public AnimationFilter getAnimationFilter() {
     90             return mAnimationFilter;
     91         }
     92     };
     93 
     94     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
     95         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
     96 
     97         @Override
     98         public AnimationFilter getAnimationFilter() {
     99             return mAnimationFilter;
    100         }
    101     }.setDuration(200).setDelay(50);
    102 
    103     /**
    104      * The animation property used for all icons that were not isolated, when the isolation ends.
    105      * This just fades the alpha and doesn't affect the movement and has a delay.
    106      */
    107     private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS
    108             = new AnimationProperties() {
    109         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
    110 
    111         @Override
    112         public AnimationFilter getAnimationFilter() {
    113             return mAnimationFilter;
    114         }
    115     }.setDuration(CONTENT_FADE_DURATION);
    116 
    117     /**
    118      * The animation property used for the icon when its isolation ends.
    119      * This animates the translation back to the right position.
    120      */
    121     private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() {
    122         private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
    123 
    124         @Override
    125         public AnimationFilter getAnimationFilter() {
    126             return mAnimationFilter;
    127         }
    128     }.setDuration(CONTENT_FADE_DURATION);
    129 
    130     public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
    131     public static final int MAX_STATIC_ICONS = 4;
    132     private static final int MAX_DOTS = 1;
    133 
    134     private boolean mIsStaticLayout = true;
    135     private final HashMap<View, IconState> mIconStates = new HashMap<>();
    136     private int mDotPadding;
    137     private int mStaticDotRadius;
    138     private int mStaticDotDiameter;
    139     private int mOverflowWidth;
    140     private int mActualLayoutWidth = NO_VALUE;
    141     private float mActualPaddingEnd = NO_VALUE;
    142     private float mActualPaddingStart = NO_VALUE;
    143     private boolean mDark;
    144     private boolean mChangingViewPositions;
    145     private int mAddAnimationStartIndex = -1;
    146     private int mCannedAnimationStartIndex = -1;
    147     private int mSpeedBumpIndex = -1;
    148     private int mIconSize;
    149     private float mOpenedAmount = 0.0f;
    150     private boolean mDisallowNextAnimation;
    151     private boolean mAnimationsEnabled = true;
    152     private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
    153     // Keep track of the last visible icon so collapsed container can report on its location
    154     private IconState mLastVisibleIconState;
    155     private IconState mFirstVisibleIconState;
    156     private float mVisualOverflowStart;
    157     // Keep track of overflow in range [0, 3]
    158     private int mNumDots;
    159     private StatusBarIconView mIsolatedIcon;
    160     private Rect mIsolatedIconLocation;
    161     private int[] mAbsolutePosition = new int[2];
    162     private View mIsolatedIconForAnimation;
    163 
    164     public NotificationIconContainer(Context context, AttributeSet attrs) {
    165         super(context, attrs);
    166         initDimens();
    167         setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW));
    168     }
    169 
    170     private void initDimens() {
    171         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
    172         mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
    173         mStaticDotDiameter = 2 * mStaticDotRadius;
    174     }
    175 
    176     @Override
    177     protected void onDraw(Canvas canvas) {
    178         super.onDraw(canvas);
    179         Paint paint = new Paint();
    180         paint.setColor(Color.RED);
    181         paint.setStyle(Paint.Style.STROKE);
    182         canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint);
    183 
    184         if (DEBUG_OVERFLOW) {
    185             if (mLastVisibleIconState == null) {
    186                 return;
    187             }
    188 
    189             int height = getHeight();
    190             int end = getFinalTranslationX();
    191 
    192             // Visualize the "end" of the layout
    193             paint.setColor(Color.BLUE);
    194             canvas.drawLine(end, 0, end, height, paint);
    195 
    196             paint.setColor(Color.GREEN);
    197             int lastIcon = (int) mLastVisibleIconState.xTranslation;
    198             canvas.drawLine(lastIcon, 0, lastIcon, height, paint);
    199 
    200             if (mFirstVisibleIconState != null) {
    201                 int firstIcon = (int) mFirstVisibleIconState.xTranslation;
    202                 canvas.drawLine(firstIcon, 0, firstIcon, height, paint);
    203             }
    204 
    205             paint.setColor(Color.RED);
    206             canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint);
    207 
    208             paint.setColor(Color.YELLOW);
    209             float overflow = getMaxOverflowStart();
    210             canvas.drawLine(overflow, 0, overflow, height, paint);
    211         }
    212     }
    213 
    214     @Override
    215     protected void onConfigurationChanged(Configuration newConfig) {
    216         super.onConfigurationChanged(newConfig);
    217         initDimens();
    218     }
    219 
    220     @Override
    221     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    222         float centerY = getHeight() / 2.0f;
    223         // we layout all our children on the left at the top
    224         mIconSize = 0;
    225         for (int i = 0; i < getChildCount(); i++) {
    226             View child = getChildAt(i);
    227             // We need to layout all children even the GONE ones, such that the heights are
    228             // calculated correctly as they are used to calculate how many we can fit on the screen
    229             int width = child.getMeasuredWidth();
    230             int height = child.getMeasuredHeight();
    231             int top = (int) (centerY - height / 2.0f);
    232             child.layout(0, top, width, top + height);
    233             if (i == 0) {
    234                 setIconSize(child.getWidth());
    235             }
    236         }
    237         getLocationOnScreen(mAbsolutePosition);
    238         if (mIsStaticLayout) {
    239             updateState();
    240         }
    241     }
    242 
    243     private void setIconSize(int size) {
    244         mIconSize = size;
    245         mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
    246     }
    247 
    248     private void updateState() {
    249         resetViewStates();
    250         calculateIconTranslations();
    251         applyIconStates();
    252     }
    253 
    254     public void applyIconStates() {
    255         for (int i = 0; i < getChildCount(); i++) {
    256             View child = getChildAt(i);
    257             ViewState childState = mIconStates.get(child);
    258             if (childState != null) {
    259                 childState.applyToView(child);
    260             }
    261         }
    262         mAddAnimationStartIndex = -1;
    263         mCannedAnimationStartIndex = -1;
    264         mDisallowNextAnimation = false;
    265         mIsolatedIconForAnimation = null;
    266     }
    267 
    268     @Override
    269     public void onViewAdded(View child) {
    270         super.onViewAdded(child);
    271         boolean isReplacingIcon = isReplacingIcon(child);
    272         if (!mChangingViewPositions) {
    273             IconState v = new IconState();
    274             if (isReplacingIcon) {
    275                 v.justAdded = false;
    276                 v.justReplaced = true;
    277             }
    278             mIconStates.put(child, v);
    279         }
    280         int childIndex = indexOfChild(child);
    281         if (childIndex < getChildCount() - 1 && !isReplacingIcon
    282             && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
    283             if (mAddAnimationStartIndex < 0) {
    284                 mAddAnimationStartIndex = childIndex;
    285             } else {
    286                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
    287             }
    288         }
    289         if (child instanceof StatusBarIconView) {
    290             ((StatusBarIconView) child).setDark(mDark, false, 0);
    291         }
    292     }
    293 
    294     private boolean isReplacingIcon(View child) {
    295         if (mReplacingIcons == null) {
    296             return false;
    297         }
    298         if (!(child instanceof StatusBarIconView)) {
    299             return false;
    300         }
    301         StatusBarIconView iconView = (StatusBarIconView) child;
    302         Icon sourceIcon = iconView.getSourceIcon();
    303         String groupKey = iconView.getNotification().getGroupKey();
    304         ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
    305         if (statusBarIcons != null) {
    306             StatusBarIcon replacedIcon = statusBarIcons.get(0);
    307             if (sourceIcon.sameAs(replacedIcon.icon)) {
    308                 return true;
    309             }
    310         }
    311         return false;
    312     }
    313 
    314     @Override
    315     public void onViewRemoved(View child) {
    316         super.onViewRemoved(child);
    317         if (child instanceof StatusBarIconView) {
    318             boolean isReplacingIcon = isReplacingIcon(child);
    319             final StatusBarIconView icon = (StatusBarIconView) child;
    320             if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
    321                     && child.getVisibility() == VISIBLE && isReplacingIcon) {
    322                 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
    323                 if (mAddAnimationStartIndex < 0) {
    324                     mAddAnimationStartIndex = animationStartIndex;
    325                 } else {
    326                     mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex);
    327                 }
    328             }
    329             if (!mChangingViewPositions) {
    330                 mIconStates.remove(child);
    331                 if (!isReplacingIcon) {
    332                     addTransientView(icon, 0);
    333                     boolean isIsolatedIcon = child == mIsolatedIcon;
    334                     icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
    335                             () -> removeTransientView(icon),
    336                             isIsolatedIcon ? CONTENT_FADE_DURATION : 0);
    337                 }
    338             }
    339         }
    340     }
    341 
    342     /**
    343      * Finds the first view with a translation bigger then a given value
    344      */
    345     private int findFirstViewIndexAfter(float translationX) {
    346         for (int i = 0; i < getChildCount(); i++) {
    347             View view = getChildAt(i);
    348             if (view.getTranslationX() > translationX) {
    349                 return i;
    350             }
    351         }
    352         return getChildCount();
    353     }
    354 
    355     public void resetViewStates() {
    356         for (int i = 0; i < getChildCount(); i++) {
    357             View view = getChildAt(i);
    358             ViewState iconState = mIconStates.get(view);
    359             iconState.initFrom(view);
    360             iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f;
    361             iconState.hidden = false;
    362         }
    363     }
    364 
    365     /**
    366      * Calculate the horizontal translations for each notification based on how much the icons
    367      * are inserted into the notification container.
    368      * If this is not a whole number, the fraction means by how much the icon is appearing.
    369      */
    370     public void calculateIconTranslations() {
    371         float translationX = getActualPaddingStart();
    372         int firstOverflowIndex = -1;
    373         int childCount = getChildCount();
    374         int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
    375                     mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
    376         float layoutEnd = getLayoutEnd();
    377         float overflowStart = getMaxOverflowStart();
    378         mVisualOverflowStart = 0;
    379         mFirstVisibleIconState = null;
    380         boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
    381         for (int i = 0; i < childCount; i++) {
    382             View view = getChildAt(i);
    383             IconState iconState = mIconStates.get(view);
    384             iconState.xTranslation = translationX;
    385             if (mFirstVisibleIconState == null) {
    386                 mFirstVisibleIconState = iconState;
    387             }
    388             boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
    389                     && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
    390             boolean noOverflowAfter = i == childCount - 1;
    391             float drawingScale = mDark && view instanceof StatusBarIconView
    392                     ? ((StatusBarIconView) view).getIconScaleFullyDark()
    393                     : 1f;
    394             if (mOpenedAmount != 0.0f) {
    395                 noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow;
    396             }
    397             iconState.visibleState = StatusBarIconView.STATE_ICON;
    398 
    399             boolean isOverflowing =
    400                     (translationX > (noOverflowAfter ? layoutEnd - mIconSize
    401                             : overflowStart - mIconSize));
    402             if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
    403                 firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
    404                 mVisualOverflowStart = layoutEnd - mOverflowWidth;
    405                 if (forceOverflow || mIsStaticLayout) {
    406                     mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
    407                 }
    408             }
    409             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
    410         }
    411         mNumDots = 0;
    412         if (firstOverflowIndex != -1) {
    413             translationX = mVisualOverflowStart;
    414             for (int i = firstOverflowIndex; i < childCount; i++) {
    415                 View view = getChildAt(i);
    416                 IconState iconState = mIconStates.get(view);
    417                 int dotWidth = mStaticDotDiameter + mDotPadding;
    418                 iconState.xTranslation = translationX;
    419                 if (mNumDots < MAX_DOTS) {
    420                     if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
    421                         iconState.visibleState = StatusBarIconView.STATE_ICON;
    422                     } else {
    423                         iconState.visibleState = StatusBarIconView.STATE_DOT;
    424                         mNumDots++;
    425                     }
    426                     translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
    427                             * iconState.iconAppearAmount;
    428                     mLastVisibleIconState = iconState;
    429                 } else {
    430                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
    431                 }
    432             }
    433         } else if (childCount > 0) {
    434             View lastChild = getChildAt(childCount - 1);
    435             mLastVisibleIconState = mIconStates.get(lastChild);
    436             mFirstVisibleIconState = mIconStates.get(getChildAt(0));
    437         }
    438         boolean center = mDark;
    439         if (center && translationX < getLayoutEnd()) {
    440             float initialTranslation =
    441                     mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation;
    442             float contentWidth = getFinalTranslationX() - initialTranslation;
    443             float availableSpace = getLayoutEnd() - getActualPaddingStart();
    444             float delta = (availableSpace - contentWidth) / 2;
    445 
    446             if (firstOverflowIndex != -1) {
    447                 // If we have an overflow, only count those half for centering because the dots
    448                 // don't have a lot of visual weight.
    449                 float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2;
    450                 delta = (deltaIgnoringOverflow + delta) / 2;
    451             }
    452             for (int i = 0; i < childCount; i++) {
    453                 View view = getChildAt(i);
    454                 IconState iconState = mIconStates.get(view);
    455                 iconState.xTranslation += delta;
    456             }
    457         }
    458 
    459         if (isLayoutRtl()) {
    460             for (int i = 0; i < childCount; i++) {
    461                 View view = getChildAt(i);
    462                 IconState iconState = mIconStates.get(view);
    463                 iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
    464             }
    465         }
    466         if (mIsolatedIcon != null) {
    467             IconState iconState = mIconStates.get(mIsolatedIcon);
    468             if (iconState != null) {
    469                 // Most of the time the icon isn't yet added when this is called but only happening
    470                 // later
    471                 iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0]
    472                         - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f;
    473                 iconState.visibleState = StatusBarIconView.STATE_ICON;
    474             }
    475         }
    476     }
    477 
    478     private float getLayoutEnd() {
    479         return getActualWidth() - getActualPaddingEnd();
    480     }
    481 
    482     private float getActualPaddingEnd() {
    483         if (mActualPaddingEnd == NO_VALUE) {
    484             return getPaddingEnd();
    485         }
    486         return mActualPaddingEnd;
    487     }
    488 
    489     private float getActualPaddingStart() {
    490         if (mActualPaddingStart == NO_VALUE) {
    491             return getPaddingStart();
    492         }
    493         return mActualPaddingStart;
    494     }
    495 
    496     /**
    497      * Sets whether the layout should always show the same number of icons.
    498      * If this is true, the icon positions will be updated on layout.
    499      * If this if false, the layout is managed from the outside and layouting won't trigger a
    500      * repositioning of the icons.
    501      */
    502     public void setIsStaticLayout(boolean isStaticLayout) {
    503         mIsStaticLayout = isStaticLayout;
    504     }
    505 
    506     public void setActualLayoutWidth(int actualLayoutWidth) {
    507         mActualLayoutWidth = actualLayoutWidth;
    508         if (DEBUG) {
    509             invalidate();
    510         }
    511     }
    512 
    513     public void setActualPaddingEnd(float paddingEnd) {
    514         mActualPaddingEnd = paddingEnd;
    515         if (DEBUG) {
    516             invalidate();
    517         }
    518     }
    519 
    520     public void setActualPaddingStart(float paddingStart) {
    521         mActualPaddingStart = paddingStart;
    522         if (DEBUG) {
    523             invalidate();
    524         }
    525     }
    526 
    527     public int getActualWidth() {
    528         if (mActualLayoutWidth == NO_VALUE) {
    529             return getWidth();
    530         }
    531         return mActualLayoutWidth;
    532     }
    533 
    534     public int getFinalTranslationX() {
    535         if (mLastVisibleIconState == null) {
    536             return 0;
    537         }
    538 
    539         int translation = (int) (mLastVisibleIconState.xTranslation + mIconSize);
    540         // There's a chance that last translation goes beyond the edge maybe
    541         return Math.min(getWidth(), translation);
    542     }
    543 
    544     private float getMaxOverflowStart() {
    545         return getLayoutEnd() - mOverflowWidth;
    546     }
    547 
    548     public void setChangingViewPositions(boolean changingViewPositions) {
    549         mChangingViewPositions = changingViewPositions;
    550     }
    551 
    552     public void setDark(boolean dark, boolean fade, long delay) {
    553         mDark = dark;
    554         mDisallowNextAnimation |= !fade;
    555         for (int i = 0; i < getChildCount(); i++) {
    556             View view = getChildAt(i);
    557             if (view instanceof StatusBarIconView) {
    558                 ((StatusBarIconView) view).setDark(dark, fade, delay);
    559             }
    560         }
    561     }
    562 
    563     public IconState getIconState(StatusBarIconView icon) {
    564         return mIconStates.get(icon);
    565     }
    566 
    567     public void setSpeedBumpIndex(int speedBumpIndex) {
    568         mSpeedBumpIndex = speedBumpIndex;
    569     }
    570 
    571     public void setOpenedAmount(float expandAmount) {
    572         mOpenedAmount = expandAmount;
    573     }
    574 
    575     public boolean hasOverflow() {
    576         return mNumDots > 0;
    577     }
    578 
    579     /**
    580      * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
    581      * extra padding will have to be accounted for
    582      *
    583      * This method has no meaning for non-static containers
    584      */
    585     public boolean hasPartialOverflow() {
    586         return mNumDots > 0 && mNumDots < MAX_DOTS;
    587     }
    588 
    589     /**
    590      * Get padding that can account for extra dots up to the max. The only valid values for
    591      * this method are for 1 or 2 dots.
    592      * @return only extraDotPadding or extraDotPadding * 2
    593      */
    594     public int getPartialOverflowExtraPadding() {
    595         if (!hasPartialOverflow()) {
    596             return 0;
    597         }
    598 
    599         int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding);
    600 
    601         int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
    602         // In case we actually give too much padding...
    603         if (adjustedWidth > getWidth()) {
    604             partialOverflowAmount = getWidth() - getFinalTranslationX();
    605         }
    606 
    607         return partialOverflowAmount;
    608     }
    609 
    610     // Give some extra room for btw notifications if we can
    611     public int getNoOverflowExtraPadding() {
    612         if (mNumDots != 0) {
    613             return 0;
    614         }
    615 
    616         int collapsedPadding = mOverflowWidth;
    617 
    618         if (collapsedPadding + getFinalTranslationX() > getWidth()) {
    619             collapsedPadding = getWidth() - getFinalTranslationX();
    620         }
    621 
    622         return collapsedPadding;
    623     }
    624 
    625     public int getIconSize() {
    626         return mIconSize;
    627     }
    628 
    629     public void setAnimationsEnabled(boolean enabled) {
    630         if (!enabled && mAnimationsEnabled) {
    631             for (int i = 0; i < getChildCount(); i++) {
    632                 View child = getChildAt(i);
    633                 ViewState childState = mIconStates.get(child);
    634                 if (childState != null) {
    635                     childState.cancelAnimations(child);
    636                     childState.applyToView(child);
    637                 }
    638             }
    639         }
    640         mAnimationsEnabled = enabled;
    641     }
    642 
    643     public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
    644         mReplacingIcons = replacingIcons;
    645     }
    646 
    647     public void showIconIsolated(StatusBarIconView icon, boolean animated) {
    648         if (animated) {
    649             mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
    650         }
    651         mIsolatedIcon = icon;
    652         updateState();
    653     }
    654 
    655     public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) {
    656         mIsolatedIconLocation = isolatedIconLocation;
    657         if (requireUpdate) {
    658             updateState();
    659         }
    660     }
    661 
    662     public class IconState extends ViewState {
    663         public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
    664         public float iconAppearAmount = 1.0f;
    665         public float clampedAppearAmount = 1.0f;
    666         public int visibleState;
    667         public boolean justAdded = true;
    668         private boolean justReplaced;
    669         public boolean needsCannedAnimation;
    670         public boolean useFullTransitionAmount;
    671         public boolean useLinearTransitionAmount;
    672         public boolean translateContent;
    673         public int iconColor = StatusBarIconView.NO_COLOR;
    674         public boolean noAnimations;
    675         public boolean isLastExpandIcon;
    676         public int customTransformHeight = NO_VALUE;
    677 
    678         @Override
    679         public void applyToView(View view) {
    680             if (view instanceof StatusBarIconView) {
    681                 StatusBarIconView icon = (StatusBarIconView) view;
    682                 boolean animate = false;
    683                 AnimationProperties animationProperties = null;
    684                 boolean animationsAllowed = mAnimationsEnabled && !mDisallowNextAnimation
    685                         && !noAnimations;
    686                 if (animationsAllowed) {
    687                     if (justAdded || justReplaced) {
    688                         super.applyToView(icon);
    689                         if (justAdded && iconAppearAmount != 0.0f) {
    690                             icon.setAlpha(0.0f);
    691                             icon.setVisibleState(StatusBarIconView.STATE_HIDDEN,
    692                                     false /* animate */);
    693                             animationProperties = ADD_ICON_PROPERTIES;
    694                             animate = true;
    695                         }
    696                     } else if (visibleState != icon.getVisibleState()) {
    697                         animationProperties = DOT_ANIMATION_PROPERTIES;
    698                         animate = true;
    699                     }
    700                     if (!animate && mAddAnimationStartIndex >= 0
    701                             && indexOfChild(view) >= mAddAnimationStartIndex
    702                             && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
    703                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
    704                         animationProperties = DOT_ANIMATION_PROPERTIES;
    705                         animate = true;
    706                     }
    707                     if (needsCannedAnimation) {
    708                         AnimationFilter animationFilter = sTempProperties.getAnimationFilter();
    709                         animationFilter.reset();
    710                         animationFilter.combineFilter(
    711                                 ICON_ANIMATION_PROPERTIES.getAnimationFilter());
    712                         sTempProperties.resetCustomInterpolators();
    713                         sTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES);
    714                         if (animationProperties != null) {
    715                             animationFilter.combineFilter(animationProperties.getAnimationFilter());
    716                             sTempProperties.combineCustomInterpolators(animationProperties);
    717                         }
    718                         animationProperties = sTempProperties;
    719                         animationProperties.setDuration(CANNED_ANIMATION_DURATION);
    720                         animate = true;
    721                         mCannedAnimationStartIndex = indexOfChild(view);
    722                     }
    723                     if (!animate && mCannedAnimationStartIndex >= 0
    724                             && indexOfChild(view) > mCannedAnimationStartIndex
    725                             && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
    726                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
    727                         AnimationFilter animationFilter = sTempProperties.getAnimationFilter();
    728                         animationFilter.reset();
    729                         animationFilter.animateX();
    730                         sTempProperties.resetCustomInterpolators();
    731                         animationProperties = sTempProperties;
    732                         animationProperties.setDuration(CANNED_ANIMATION_DURATION);
    733                         animate = true;
    734                     }
    735                     if (mIsolatedIconForAnimation != null) {
    736                         if (view == mIsolatedIconForAnimation) {
    737                             animationProperties = UNISOLATION_PROPERTY;
    738                             animationProperties.setDelay(
    739                                     mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
    740                         } else {
    741                             animationProperties = UNISOLATION_PROPERTY_OTHERS;
    742                             animationProperties.setDelay(
    743                                     mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0);
    744                         }
    745                         animate = true;
    746                     }
    747                 }
    748                 icon.setVisibleState(visibleState, animationsAllowed);
    749                 icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed);
    750                 if (animate) {
    751                     animateTo(icon, animationProperties);
    752                 } else {
    753                     super.applyToView(view);
    754                 }
    755                 boolean inShelf = iconAppearAmount == 1.0f;
    756                 icon.setIsInShelf(inShelf);
    757             }
    758             justAdded = false;
    759             justReplaced = false;
    760             needsCannedAnimation = false;
    761         }
    762 
    763         public boolean hasCustomTransformHeight() {
    764             return isLastExpandIcon && customTransformHeight != NO_VALUE;
    765         }
    766 
    767         @Override
    768         public void initFrom(View view) {
    769             super.initFrom(view);
    770             if (view instanceof StatusBarIconView) {
    771                 iconColor = ((StatusBarIconView) view).getStaticDrawableColor();
    772             }
    773         }
    774     }
    775 }
    776