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