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 android.content.Context;
20 import android.content.res.Configuration;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.drawable.Icon;
25 import android.support.v4.util.ArrayMap;
26 import android.support.v4.util.ArraySet;
27 import android.util.AttributeSet;
28 import android.view.View;
29 
30 import com.android.internal.statusbar.StatusBarIcon;
31 import com.android.systemui.Interpolators;
32 import com.android.systemui.R;
33 import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
34 import com.android.systemui.statusbar.StatusBarIconView;
35 import com.android.systemui.statusbar.stack.AnimationFilter;
36 import com.android.systemui.statusbar.stack.AnimationProperties;
37 import com.android.systemui.statusbar.stack.StackStateAnimator;
38 import com.android.systemui.statusbar.stack.ViewState;
39 
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 
43 /**
44  * A container for notification icons. It handles overflowing icons properly and positions them
45  * correctly on the screen.
46  */
47 public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
48     /**
49      * A float value indicating how much before the overflow start the icons should transform into
50      * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
51      * 1 icon width early.
52      */
53     public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
54     private static final int NO_VALUE = Integer.MIN_VALUE;
55     private static final String TAG = "NotificationIconContainer";
56     private static final boolean DEBUG = false;
57     private static final int CANNED_ANIMATION_DURATION = 100;
58     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
59         private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
60 
61         @Override
62         public AnimationFilter getAnimationFilter() {
63             return mAnimationFilter;
64         }
65     }.setDuration(200);
66 
67     private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
68         private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha()
69                 .animateScale();
70 
71         @Override
72         public AnimationFilter getAnimationFilter() {
73             return mAnimationFilter;
74         }
75 
76     }.setDuration(CANNED_ANIMATION_DURATION)
77             .setCustomInterpolator(View.TRANSLATION_Y, Interpolators.ICON_OVERSHOT);
78 
79     private static final AnimationProperties mTempProperties = new AnimationProperties() {
80         private AnimationFilter mAnimationFilter = new AnimationFilter();
81 
82         @Override
83         public AnimationFilter getAnimationFilter() {
84             return mAnimationFilter;
85         }
86     };
87 
88     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
89         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
90 
91         @Override
92         public AnimationFilter getAnimationFilter() {
93             return mAnimationFilter;
94         }
95     }.setDuration(200).setDelay(50);
96 
97     private static final AnimationProperties UNDARK_PROPERTIES = new AnimationProperties() {
98         private AnimationFilter mAnimationFilter = new AnimationFilter()
99                 .animateX();
100 
101         @Override
102         public AnimationFilter getAnimationFilter() {
103             return mAnimationFilter;
104         }
105     }.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
106     public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
107 
108     private boolean mShowAllIcons = true;
109     private final HashMap<View, IconState> mIconStates = new HashMap<>();
110     private int mDotPadding;
111     private int mStaticDotRadius;
112     private int mActualLayoutWidth = NO_VALUE;
113     private float mActualPaddingEnd = NO_VALUE;
114     private float mActualPaddingStart = NO_VALUE;
115     private boolean mDark;
116     private boolean mChangingViewPositions;
117     private int mAddAnimationStartIndex = -1;
118     private int mCannedAnimationStartIndex = -1;
119     private int mSpeedBumpIndex = -1;
120     private int mIconSize;
121     private float mOpenedAmount = 0.0f;
122     private float mVisualOverflowAdaption;
123     private boolean mDisallowNextAnimation;
124     private boolean mAnimationsEnabled = true;
125     private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
126 
NotificationIconContainer(Context context, AttributeSet attrs)127     public NotificationIconContainer(Context context, AttributeSet attrs) {
128         super(context, attrs);
129         initDimens();
130         setWillNotDraw(!DEBUG);
131     }
132 
initDimens()133     private void initDimens() {
134         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
135         mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
136     }
137 
138     @Override
onDraw(Canvas canvas)139     protected void onDraw(Canvas canvas) {
140         super.onDraw(canvas);
141         Paint paint = new Paint();
142         paint.setColor(Color.RED);
143         paint.setStyle(Paint.Style.STROKE);
144         canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint);
145     }
146 
147     @Override
onConfigurationChanged(Configuration newConfig)148     protected void onConfigurationChanged(Configuration newConfig) {
149         super.onConfigurationChanged(newConfig);
150         initDimens();
151     }
152     @Override
onLayout(boolean changed, int l, int t, int r, int b)153     protected void onLayout(boolean changed, int l, int t, int r, int b) {
154         float centerY = getHeight() / 2.0f;
155         // we layout all our children on the left at the top
156         mIconSize = 0;
157         for (int i = 0; i < getChildCount(); i++) {
158             View child = getChildAt(i);
159             // We need to layout all children even the GONE ones, such that the heights are
160             // calculated correctly as they are used to calculate how many we can fit on the screen
161             int width = child.getMeasuredWidth();
162             int height = child.getMeasuredHeight();
163             int top = (int) (centerY - height / 2.0f);
164             child.layout(0, top, width, top + height);
165             if (i == 0) {
166                 mIconSize = child.getWidth();
167             }
168         }
169         if (mShowAllIcons) {
170             resetViewStates();
171             calculateIconTranslations();
172             applyIconStates();
173         }
174     }
175 
applyIconStates()176     public void applyIconStates() {
177         for (int i = 0; i < getChildCount(); i++) {
178             View child = getChildAt(i);
179             ViewState childState = mIconStates.get(child);
180             if (childState != null) {
181                 childState.applyToView(child);
182             }
183         }
184         mAddAnimationStartIndex = -1;
185         mCannedAnimationStartIndex = -1;
186         mDisallowNextAnimation = false;
187     }
188 
189     @Override
onViewAdded(View child)190     public void onViewAdded(View child) {
191         super.onViewAdded(child);
192         boolean isReplacingIcon = isReplacingIcon(child);
193         if (!mChangingViewPositions) {
194             IconState v = new IconState();
195             if (isReplacingIcon) {
196                 v.justAdded = false;
197                 v.justReplaced = true;
198             }
199             mIconStates.put(child, v);
200         }
201         int childIndex = indexOfChild(child);
202         if (childIndex < getChildCount() - 1 && !isReplacingIcon
203             && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
204             if (mAddAnimationStartIndex < 0) {
205                 mAddAnimationStartIndex = childIndex;
206             } else {
207                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
208             }
209         }
210         if (mDark && child instanceof StatusBarIconView) {
211             ((StatusBarIconView) child).setDark(mDark, false, 0);
212         }
213     }
214 
isReplacingIcon(View child)215     private boolean isReplacingIcon(View child) {
216         if (mReplacingIcons == null) {
217             return false;
218         }
219         if (!(child instanceof StatusBarIconView)) {
220             return false;
221         }
222         StatusBarIconView iconView = (StatusBarIconView) child;
223         Icon sourceIcon = iconView.getSourceIcon();
224         String groupKey = iconView.getNotification().getGroupKey();
225         ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
226         if (statusBarIcons != null) {
227             StatusBarIcon replacedIcon = statusBarIcons.get(0);
228             if (sourceIcon.sameAs(replacedIcon.icon)) {
229                 return true;
230             }
231         }
232         return false;
233     }
234 
235     @Override
onViewRemoved(View child)236     public void onViewRemoved(View child) {
237         super.onViewRemoved(child);
238         if (child instanceof StatusBarIconView) {
239             boolean isReplacingIcon = isReplacingIcon(child);
240             final StatusBarIconView icon = (StatusBarIconView) child;
241             if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
242                     && child.getVisibility() == VISIBLE && isReplacingIcon) {
243                 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
244                 if (mAddAnimationStartIndex < 0) {
245                     mAddAnimationStartIndex = animationStartIndex;
246                 } else {
247                     mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex);
248                 }
249             }
250             if (!mChangingViewPositions) {
251                 mIconStates.remove(child);
252                 if (!isReplacingIcon) {
253                     addTransientView(icon, 0);
254                     icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
255                             () -> removeTransientView(icon));
256                 }
257             }
258         }
259     }
260 
261     /**
262      * Finds the first view with a translation bigger then a given value
263      */
findFirstViewIndexAfter(float translationX)264     private int findFirstViewIndexAfter(float translationX) {
265         for (int i = 0; i < getChildCount(); i++) {
266             View view = getChildAt(i);
267             if (view.getTranslationX() > translationX) {
268                 return i;
269             }
270         }
271         return getChildCount();
272     }
273 
resetViewStates()274     public void resetViewStates() {
275         for (int i = 0; i < getChildCount(); i++) {
276             View view = getChildAt(i);
277             ViewState iconState = mIconStates.get(view);
278             iconState.initFrom(view);
279             iconState.alpha = 1.0f;
280             iconState.hidden = false;
281         }
282     }
283 
284     /**
285      * Calulate the horizontal translations for each notification based on how much the icons
286      * are inserted into the notification container.
287      * If this is not a whole number, the fraction means by how much the icon is appearing.
288      */
calculateIconTranslations()289     public void calculateIconTranslations() {
290         float translationX = getActualPaddingStart();
291         int firstOverflowIndex = -1;
292         int childCount = getChildCount();
293         int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
294         float layoutEnd = getLayoutEnd();
295         float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
296         boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
297         float visualOverflowStart = 0;
298         for (int i = 0; i < childCount; i++) {
299             View view = getChildAt(i);
300             IconState iconState = mIconStates.get(view);
301             iconState.xTranslation = translationX;
302             boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
303                     && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
304             boolean noOverflowAfter = i == childCount - 1;
305             float drawingScale = mDark && view instanceof StatusBarIconView
306                     ? ((StatusBarIconView) view).getIconScaleFullyDark()
307                     : 1f;
308             if (mOpenedAmount != 0.0f) {
309                 noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow;
310             }
311             iconState.visibleState = StatusBarIconView.STATE_ICON;
312             if (firstOverflowIndex == -1 && (forceOverflow
313                     || (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart)))) {
314                 firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
315                 int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
316                 visualOverflowStart = overflowStart + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)
317                         - totalDotLength / 2
318                         - mIconSize * 0.5f + mStaticDotRadius;
319                 if (forceOverflow) {
320                     visualOverflowStart = Math.min(translationX, visualOverflowStart
321                             + mStaticDotRadius * 2 + mDotPadding);
322                 } else {
323                     visualOverflowStart += (translationX - overflowStart) / mIconSize
324                             * (mStaticDotRadius * 2 + mDotPadding);
325                 }
326                 if (mShowAllIcons) {
327                     // We want to perfectly position the overflow in the static state, such that
328                     // it's perfectly centered instead of measuring it from the end.
329                     mVisualOverflowAdaption = 0;
330                     if (firstOverflowIndex != -1) {
331                         View firstOverflowView = getChildAt(i);
332                         IconState overflowState = mIconStates.get(firstOverflowView);
333                         float totalAmount = layoutEnd - overflowState.xTranslation;
334                         float newPosition = overflowState.xTranslation + totalAmount / 2
335                                 - totalDotLength / 2
336                                 - mIconSize * 0.5f + mStaticDotRadius;
337                         mVisualOverflowAdaption = newPosition - visualOverflowStart;
338                         visualOverflowStart = newPosition;
339                     }
340                 } else {
341                     visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
342                 }
343             }
344             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
345         }
346         if (firstOverflowIndex != -1) {
347             int numDots = 1;
348             translationX = visualOverflowStart;
349             for (int i = firstOverflowIndex; i < childCount; i++) {
350                 View view = getChildAt(i);
351                 IconState iconState = mIconStates.get(view);
352                 int dotWidth = mStaticDotRadius * 2 + mDotPadding;
353                 iconState.xTranslation = translationX;
354                 if (numDots <= 3) {
355                     if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
356                         iconState.visibleState = StatusBarIconView.STATE_ICON;
357                         numDots--;
358                     } else {
359                         iconState.visibleState = StatusBarIconView.STATE_DOT;
360                     }
361                     translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
362                             * iconState.iconAppearAmount;
363                 } else {
364                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
365                 }
366                 numDots++;
367             }
368         }
369         boolean center = mDark;
370         if (center && translationX < getLayoutEnd()) {
371             float delta = (getLayoutEnd() - translationX) / 2;
372             if (firstOverflowIndex != -1) {
373                 // If we have an overflow, only count those half for centering because the dots
374                 // don't have a lot of visual weight.
375                 float deltaIgnoringOverflow = (getLayoutEnd() - visualOverflowStart) / 2;
376                 delta = (deltaIgnoringOverflow + delta) / 2;
377             }
378             for (int i = 0; i < childCount; i++) {
379                 View view = getChildAt(i);
380                 IconState iconState = mIconStates.get(view);
381                 iconState.xTranslation += delta;
382             }
383         }
384 
385         if (isLayoutRtl()) {
386             for (int i = 0; i < childCount; i++) {
387                 View view = getChildAt(i);
388                 IconState iconState = mIconStates.get(view);
389                 iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
390             }
391         }
392     }
393 
getLayoutEnd()394     private float getLayoutEnd() {
395         return getActualWidth() - getActualPaddingEnd();
396     }
397 
getActualPaddingEnd()398     private float getActualPaddingEnd() {
399         if (mActualPaddingEnd == NO_VALUE) {
400             return getPaddingEnd();
401         }
402         return mActualPaddingEnd;
403     }
404 
getActualPaddingStart()405     private float getActualPaddingStart() {
406         if (mActualPaddingStart == NO_VALUE) {
407             return getPaddingStart();
408         }
409         return mActualPaddingStart;
410     }
411 
412     /**
413      * Sets whether the layout should always show all icons.
414      * If this is true, the icon positions will be updated on layout.
415      * If this if false, the layout is managed from the outside and layouting won't trigger a
416      * repositioning of the icons.
417      */
setShowAllIcons(boolean showAllIcons)418     public void setShowAllIcons(boolean showAllIcons) {
419         mShowAllIcons = showAllIcons;
420     }
421 
setActualLayoutWidth(int actualLayoutWidth)422     public void setActualLayoutWidth(int actualLayoutWidth) {
423         mActualLayoutWidth = actualLayoutWidth;
424         if (DEBUG) {
425             invalidate();
426         }
427     }
428 
setActualPaddingEnd(float paddingEnd)429     public void setActualPaddingEnd(float paddingEnd) {
430         mActualPaddingEnd = paddingEnd;
431         if (DEBUG) {
432             invalidate();
433         }
434     }
435 
setActualPaddingStart(float paddingStart)436     public void setActualPaddingStart(float paddingStart) {
437         mActualPaddingStart = paddingStart;
438         if (DEBUG) {
439             invalidate();
440         }
441     }
442 
getActualWidth()443     public int getActualWidth() {
444         if (mActualLayoutWidth == NO_VALUE) {
445             return getWidth();
446         }
447         return mActualLayoutWidth;
448     }
449 
setChangingViewPositions(boolean changingViewPositions)450     public void setChangingViewPositions(boolean changingViewPositions) {
451         mChangingViewPositions = changingViewPositions;
452     }
453 
setDark(boolean dark, boolean fade, long delay)454     public void setDark(boolean dark, boolean fade, long delay) {
455         mDark = dark;
456         mDisallowNextAnimation |= !fade;
457         for (int i = 0; i < getChildCount(); i++) {
458             View view = getChildAt(i);
459             if (view instanceof StatusBarIconView) {
460                 ((StatusBarIconView) view).setDark(dark, fade, delay);
461                 if (!dark && fade) {
462                     getIconState((StatusBarIconView) view).justUndarkened = true;
463                 }
464             }
465         }
466     }
467 
getIconState(StatusBarIconView icon)468     public IconState getIconState(StatusBarIconView icon) {
469         return mIconStates.get(icon);
470     }
471 
setSpeedBumpIndex(int speedBumpIndex)472     public void setSpeedBumpIndex(int speedBumpIndex) {
473         mSpeedBumpIndex = speedBumpIndex;
474     }
475 
setOpenedAmount(float expandAmount)476     public void setOpenedAmount(float expandAmount) {
477         mOpenedAmount = expandAmount;
478     }
479 
getVisualOverflowAdaption()480     public float getVisualOverflowAdaption() {
481         return mVisualOverflowAdaption;
482     }
483 
setVisualOverflowAdaption(float visualOverflowAdaption)484     public void setVisualOverflowAdaption(float visualOverflowAdaption) {
485         mVisualOverflowAdaption = visualOverflowAdaption;
486     }
487 
hasOverflow()488     public boolean hasOverflow() {
489         float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
490         return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
491     }
492 
getIconSize()493     public int getIconSize() {
494         return mIconSize;
495     }
496 
setAnimationsEnabled(boolean enabled)497     public void setAnimationsEnabled(boolean enabled) {
498         if (!enabled && mAnimationsEnabled) {
499             for (int i = 0; i < getChildCount(); i++) {
500                 View child = getChildAt(i);
501                 ViewState childState = mIconStates.get(child);
502                 if (childState != null) {
503                     childState.cancelAnimations(child);
504                     childState.applyToView(child);
505                 }
506             }
507         }
508         mAnimationsEnabled = enabled;
509     }
510 
setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons)511     public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
512         mReplacingIcons = replacingIcons;
513     }
514 
515     public class IconState extends ViewState {
516         public float iconAppearAmount = 1.0f;
517         public float clampedAppearAmount = 1.0f;
518         public int visibleState;
519         public boolean justAdded = true;
520         private boolean justReplaced;
521         public boolean needsCannedAnimation;
522         public boolean useFullTransitionAmount;
523         public boolean useLinearTransitionAmount;
524         public boolean translateContent;
525         public boolean justUndarkened;
526         public int iconColor = StatusBarIconView.NO_COLOR;
527         public boolean noAnimations;
528 
529         @Override
applyToView(View view)530         public void applyToView(View view) {
531             if (view instanceof StatusBarIconView) {
532                 StatusBarIconView icon = (StatusBarIconView) view;
533                 boolean animate = false;
534                 AnimationProperties animationProperties = null;
535                 boolean animationsAllowed = (mAnimationsEnabled || justUndarkened)
536                         && !mDisallowNextAnimation
537                         && !noAnimations;
538                 if (animationsAllowed) {
539                     if (justAdded || justReplaced) {
540                         super.applyToView(icon);
541                         if (justAdded && iconAppearAmount != 0.0f) {
542                             icon.setAlpha(0.0f);
543                             icon.setVisibleState(StatusBarIconView.STATE_HIDDEN,
544                                     false /* animate */);
545                             animationProperties = ADD_ICON_PROPERTIES;
546                             animate = true;
547                         }
548                     } else if (justUndarkened) {
549                         animationProperties = UNDARK_PROPERTIES;
550                         animate = true;
551                     } else if (visibleState != icon.getVisibleState()) {
552                         animationProperties = DOT_ANIMATION_PROPERTIES;
553                         animate = true;
554                     }
555                     if (!animate && mAddAnimationStartIndex >= 0
556                             && indexOfChild(view) >= mAddAnimationStartIndex
557                             && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
558                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
559                         animationProperties = DOT_ANIMATION_PROPERTIES;
560                         animate = true;
561                     }
562                     if (needsCannedAnimation) {
563                         AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
564                         animationFilter.reset();
565                         animationFilter.combineFilter(
566                                 ICON_ANIMATION_PROPERTIES.getAnimationFilter());
567                         mTempProperties.resetCustomInterpolators();
568                         mTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES);
569                         if (animationProperties != null) {
570                             animationFilter.combineFilter(animationProperties.getAnimationFilter());
571                             mTempProperties.combineCustomInterpolators(animationProperties);
572                         }
573                         animationProperties = mTempProperties;
574                         animationProperties.setDuration(CANNED_ANIMATION_DURATION);
575                         animate = true;
576                         mCannedAnimationStartIndex = indexOfChild(view);
577                     }
578                     if (!animate && mCannedAnimationStartIndex >= 0
579                             && indexOfChild(view) > mCannedAnimationStartIndex
580                             && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
581                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
582                         AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
583                         animationFilter.reset();
584                         animationFilter.animateX();
585                         mTempProperties.resetCustomInterpolators();
586                         animationProperties = mTempProperties;
587                         animationProperties.setDuration(CANNED_ANIMATION_DURATION);
588                         animate = true;
589                     }
590                 }
591                 icon.setVisibleState(visibleState, animationsAllowed);
592                 icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed);
593                 if (animate) {
594                     animateTo(icon, animationProperties);
595                 } else {
596                     super.applyToView(view);
597                 }
598             }
599             justAdded = false;
600             justReplaced = false;
601             needsCannedAnimation = false;
602             justUndarkened = false;
603         }
604 
605         @Override
initFrom(View view)606         public void initFrom(View view) {
607             super.initFrom(view);
608             if (view instanceof StatusBarIconView) {
609                 iconColor = ((StatusBarIconView) view).getStaticDrawableColor();
610             }
611         }
612     }
613 }
614