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