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