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