• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.statusbar.phone;
2 
3 import android.app.NotificationManager;
4 import android.content.Context;
5 import android.content.res.Resources;
6 import android.graphics.Color;
7 import android.graphics.Rect;
8 import android.os.Bundle;
9 import android.view.LayoutInflater;
10 import android.view.View;
11 import android.widget.FrameLayout;
12 
13 import androidx.annotation.NonNull;
14 import androidx.annotation.VisibleForTesting;
15 import androidx.collection.ArrayMap;
16 
17 import com.android.internal.statusbar.StatusBarIcon;
18 import com.android.internal.util.ContrastColorUtil;
19 import com.android.settingslib.Utils;
20 import com.android.systemui.R;
21 import com.android.systemui.animation.Interpolators;
22 import com.android.systemui.dagger.SysUISingleton;
23 import com.android.systemui.demomode.DemoMode;
24 import com.android.systemui.demomode.DemoModeController;
25 import com.android.systemui.plugins.DarkIconDispatcher;
26 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
27 import com.android.systemui.plugins.statusbar.StatusBarStateController;
28 import com.android.systemui.statusbar.CrossFadeHelper;
29 import com.android.systemui.statusbar.NotificationListener;
30 import com.android.systemui.statusbar.NotificationMediaManager;
31 import com.android.systemui.statusbar.NotificationShelfController;
32 import com.android.systemui.statusbar.StatusBarIconView;
33 import com.android.systemui.statusbar.StatusBarState;
34 import com.android.systemui.statusbar.notification.AnimatableProperty;
35 import com.android.systemui.statusbar.notification.NotificationUtils;
36 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
37 import com.android.systemui.statusbar.notification.PropertyAnimator;
38 import com.android.systemui.statusbar.notification.collection.ListEntry;
39 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
40 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
41 import com.android.wm.shell.bubbles.Bubbles;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.Optional;
47 import java.util.function.Function;
48 
49 import javax.inject.Inject;
50 
51 /**
52  * A controller for the space in the status bar to the left of the system icons. This area is
53  * normally reserved for notifications.
54  */
55 @SysUISingleton
56 public class NotificationIconAreaController implements
57         DarkReceiver,
58         StatusBarStateController.StateListener,
59         NotificationWakeUpCoordinator.WakeUpListener,
60         DemoMode {
61 
62     public static final String HIGH_PRIORITY = "high_priority";
63     private static final long AOD_ICONS_APPEAR_DURATION = 200;
64 
65     private final ContrastColorUtil mContrastColorUtil;
66     private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
67     private final StatusBarStateController mStatusBarStateController;
68     private final NotificationMediaManager mMediaManager;
69     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
70     private final KeyguardBypassController mBypassController;
71     private final DozeParameters mDozeParameters;
72     private final Optional<Bubbles> mBubblesOptional;
73     private final StatusBarWindowController mStatusBarWindowController;
74     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
75 
76     private int mIconSize;
77     private int mIconHPadding;
78     private int mIconTint = Color.WHITE;
79     private int mCenteredIconTint = Color.WHITE;
80 
81     private List<ListEntry> mNotificationEntries = List.of();
82     protected View mNotificationIconArea;
83     private NotificationIconContainer mNotificationIcons;
84     private NotificationIconContainer mShelfIcons;
85     protected View mCenteredIconArea;
86     private NotificationIconContainer mCenteredIcon;
87     private NotificationIconContainer mAodIcons;
88     private StatusBarIconView mCenteredIconView;
89     private final Rect mTintArea = new Rect();
90     private Context mContext;
91 
92     private final DemoModeController mDemoModeController;
93 
94     private int mAodIconAppearTranslation;
95 
96     private boolean mAnimationsEnabled;
97     private int mAodIconTint;
98     private boolean mAodIconsVisible;
99     private boolean mShowLowPriority = true;
100 
101     @VisibleForTesting
102     final NotificationListener.NotificationSettingsListener mSettingsListener =
103             new NotificationListener.NotificationSettingsListener() {
104                 @Override
105                 public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
106                     mShowLowPriority = !hideSilentStatusIcons;
107                     updateStatusBarIcons();
108                 }
109             };
110 
111     @Inject
NotificationIconAreaController( Context context, StatusBarStateController statusBarStateController, NotificationWakeUpCoordinator wakeUpCoordinator, KeyguardBypassController keyguardBypassController, NotificationMediaManager notificationMediaManager, NotificationListener notificationListener, DozeParameters dozeParameters, Optional<Bubbles> bubblesOptional, DemoModeController demoModeController, DarkIconDispatcher darkIconDispatcher, StatusBarWindowController statusBarWindowController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)112     public NotificationIconAreaController(
113             Context context,
114             StatusBarStateController statusBarStateController,
115             NotificationWakeUpCoordinator wakeUpCoordinator,
116             KeyguardBypassController keyguardBypassController,
117             NotificationMediaManager notificationMediaManager,
118             NotificationListener notificationListener,
119             DozeParameters dozeParameters,
120             Optional<Bubbles> bubblesOptional,
121             DemoModeController demoModeController,
122             DarkIconDispatcher darkIconDispatcher,
123             StatusBarWindowController statusBarWindowController,
124             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
125         mContrastColorUtil = ContrastColorUtil.getInstance(context);
126         mContext = context;
127         mStatusBarStateController = statusBarStateController;
128         mStatusBarStateController.addCallback(this);
129         mMediaManager = notificationMediaManager;
130         mDozeParameters = dozeParameters;
131         mWakeUpCoordinator = wakeUpCoordinator;
132         wakeUpCoordinator.addListener(this);
133         mBypassController = keyguardBypassController;
134         mBubblesOptional = bubblesOptional;
135         mDemoModeController = demoModeController;
136         mDemoModeController.addCallback(this);
137         mStatusBarWindowController = statusBarWindowController;
138         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
139         notificationListener.addNotificationSettingsListener(mSettingsListener);
140 
141         initializeNotificationAreaViews(context);
142         reloadAodColor();
143         darkIconDispatcher.addDarkReceiver(this);
144     }
145 
inflateIconArea(LayoutInflater inflater)146     protected View inflateIconArea(LayoutInflater inflater) {
147         return inflater.inflate(R.layout.notification_icon_area, null);
148     }
149 
150     /**
151      * Initializes the views that will represent the notification area.
152      */
initializeNotificationAreaViews(Context context)153     protected void initializeNotificationAreaViews(Context context) {
154         reloadDimens(context);
155 
156         LayoutInflater layoutInflater = LayoutInflater.from(context);
157         mNotificationIconArea = inflateIconArea(layoutInflater);
158         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
159 
160         mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);
161         mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);
162     }
163 
164     /**
165      * Called by the Keyguard*ViewController whose view contains the aod icons.
166      */
setupAodIcons(@onNull NotificationIconContainer aodIcons)167     public void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
168         boolean changed = mAodIcons != null && aodIcons != mAodIcons;
169         if (changed) {
170             mAodIcons.setAnimationsEnabled(false);
171             mAodIcons.removeAllViews();
172         }
173         mAodIcons = aodIcons;
174         mAodIcons.setOnLockScreen(true);
175         updateAodIconsVisibility(false /* animate */, changed);
176         updateAnimations();
177         if (changed) {
178             updateAodNotificationIcons();
179         }
180         updateIconLayoutParams(mContext);
181     }
182 
183     /**
184      * Update position of the view, with optional animation
185      */
updatePosition(int x, AnimationProperties props, boolean animate)186     public void updatePosition(int x, AnimationProperties props, boolean animate) {
187         if (mAodIcons != null) {
188             PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
189                     animate);
190         }
191     }
192 
setupShelf(NotificationShelfController notificationShelfController)193     public void setupShelf(NotificationShelfController notificationShelfController) {
194         mShelfIcons = notificationShelfController.getShelfIcons();
195         notificationShelfController.setCollapsedIcons(mNotificationIcons);
196     }
197 
onDensityOrFontScaleChanged(Context context)198     public void onDensityOrFontScaleChanged(Context context) {
199         updateIconLayoutParams(context);
200     }
201 
updateIconLayoutParams(Context context)202     private void updateIconLayoutParams(Context context) {
203         reloadDimens(context);
204         final FrameLayout.LayoutParams params = generateIconLayoutParams();
205         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
206             View child = mNotificationIcons.getChildAt(i);
207             child.setLayoutParams(params);
208         }
209         for (int i = 0; i < mCenteredIcon.getChildCount(); i++) {
210             View child = mCenteredIcon.getChildAt(i);
211             child.setLayoutParams(params);
212         }
213         if (mShelfIcons != null) {
214             for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
215                 View child = mShelfIcons.getChildAt(i);
216                 child.setLayoutParams(params);
217             }
218         }
219         if (mAodIcons != null) {
220             for (int i = 0; i < mAodIcons.getChildCount(); i++) {
221                 View child = mAodIcons.getChildAt(i);
222                 child.setLayoutParams(params);
223             }
224         }
225     }
226 
227     @NonNull
generateIconLayoutParams()228     private FrameLayout.LayoutParams generateIconLayoutParams() {
229         return new FrameLayout.LayoutParams(
230                 mIconSize + 2 * mIconHPadding, mStatusBarWindowController.getStatusBarHeight());
231     }
232 
reloadDimens(Context context)233     private void reloadDimens(Context context) {
234         Resources res = context.getResources();
235         mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
236         mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
237         mAodIconAppearTranslation = res.getDimensionPixelSize(
238                 R.dimen.shelf_appear_translation);
239     }
240 
241     /**
242      * Returns the view that represents the notification area.
243      */
getNotificationInnerAreaView()244     public View getNotificationInnerAreaView() {
245         return mNotificationIconArea;
246     }
247 
248     /**
249      * Returns the view that represents the centered notification area.
250      */
getCenteredNotificationAreaView()251     public View getCenteredNotificationAreaView() {
252         return mCenteredIconArea;
253     }
254 
255     /**
256      * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
257      * Sets the color that should be used to tint any icons in the notification area.
258      *
259      * @param tintArea the area in which to tint the icons, specified in screen coordinates
260      * @param darkIntensity
261      */
onDarkChanged(Rect tintArea, float darkIntensity, int iconTint)262     public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
263         if (tintArea == null) {
264             mTintArea.setEmpty();
265         } else {
266             mTintArea.set(tintArea);
267         }
268 
269         if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
270             mIconTint = iconTint;
271         }
272 
273         if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
274             mCenteredIconTint = iconTint;
275         }
276 
277         applyNotificationIconsTint();
278     }
279 
shouldShowNotificationIcon(NotificationEntry entry, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon)280     protected boolean shouldShowNotificationIcon(NotificationEntry entry,
281             boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
282             boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon,
283             boolean hidePulsing, boolean onlyShowCenteredIcon) {
284 
285         final boolean isCenteredNotificationIcon = mCenteredIconView != null
286                 && entry.getIcons().getCenteredIcon() != null
287                 && Objects.equals(entry.getIcons().getCenteredIcon(), mCenteredIconView);
288         if (onlyShowCenteredIcon) {
289             return isCenteredNotificationIcon;
290         }
291         if (hideCenteredIcon && isCenteredNotificationIcon && !entry.isRowHeadsUp()) {
292             return false;
293         }
294         if (entry.getRanking().isAmbient() && !showAmbient) {
295             return false;
296         }
297         if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) {
298             return false;
299         }
300         if (!showLowPriority && entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
301             return false;
302         }
303         if (!entry.isTopLevelChild()) {
304             return false;
305         }
306         if (entry.getRow().getVisibility() == View.GONE) {
307             return false;
308         }
309         if (entry.isRowDismissed() && hideDismissed) {
310             return false;
311         }
312         if (hideRepliedMessages && entry.isLastMessageFromReply()) {
313             return false;
314         }
315         // showAmbient == show in shade but not shelf
316         if (!showAmbient && entry.shouldSuppressStatusBar()) {
317             return false;
318         }
319         if (hidePulsing && entry.showingPulsing()
320                 && (!mWakeUpCoordinator.getNotificationsFullyHidden()
321                         || !entry.isPulseSuppressed())) {
322             return false;
323         }
324         if (mBubblesOptional.isPresent()
325                 && mBubblesOptional.get().isBubbleExpanded(entry.getKey())) {
326             return false;
327         }
328         return true;
329     }
330     /**
331      * Updates the notifications with the given list of notifications to display.
332      */
updateNotificationIcons(List<ListEntry> entries)333     public void updateNotificationIcons(List<ListEntry> entries) {
334         mNotificationEntries = entries;
335         updateNotificationIcons();
336     }
337 
updateNotificationIcons()338     private void updateNotificationIcons() {
339         updateStatusBarIcons();
340         updateShelfIcons();
341         updateCenterIcon();
342         updateAodNotificationIcons();
343 
344         applyNotificationIconsTint();
345     }
346 
updateShelfIcons()347     private void updateShelfIcons() {
348         updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
349                 true /* showAmbient */,
350                 true /* showLowPriority */,
351                 false /* hideDismissed */,
352                 false /* hideRepliedMessages */,
353                 false /* hideCurrentMedia */,
354                 false /* hide centered icon */,
355                 false /* hidePulsing */,
356                 false /* onlyShowCenteredIcon */);
357     }
358 
updateStatusBarIcons()359     public void updateStatusBarIcons() {
360         updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
361                 false /* showAmbient */,
362                 mShowLowPriority,
363                 true /* hideDismissed */,
364                 true /* hideRepliedMessages */,
365                 false /* hideCurrentMedia */,
366                 true /* hide centered icon */,
367                 false /* hidePulsing */,
368                 false /* onlyShowCenteredIcon */);
369     }
370 
updateCenterIcon()371     private void updateCenterIcon() {
372         updateIconsForLayout(entry -> entry.getIcons().getCenteredIcon(), mCenteredIcon,
373                 false /* showAmbient */,
374                 true /* showLowPriority */,
375                 false /* hideDismissed */,
376                 false /* hideRepliedMessages */,
377                 false /* hideCurrentMedia */,
378                 false /* hide centered icon */,
379                 false /* hidePulsing */,
380                 true/* onlyShowCenteredIcon */);
381     }
382 
updateAodNotificationIcons()383     public void updateAodNotificationIcons() {
384         if (mAodIcons == null) {
385             return;
386         }
387         updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
388                 false /* showAmbient */,
389                 true /* showLowPriority */,
390                 true /* hideDismissed */,
391                 true /* hideRepliedMessages */,
392                 true /* hideCurrentMedia */,
393                 true /* hide centered icon */,
394                 mBypassController.getBypassEnabled() /* hidePulsing */,
395                 false /* onlyShowCenteredIcon */);
396     }
397 
398     @VisibleForTesting
shouldShouldLowPriorityIcons()399     boolean shouldShouldLowPriorityIcons() {
400         return mShowLowPriority;
401     }
402 
403     /**
404      * Updates the notification icons for a host layout. This will ensure that the notification
405      * host layout will have the same icons like the ones in here.
406      * @param function A function to look up an icon view based on an entry
407      * @param hostLayout which layout should be updated
408      * @param showAmbient should ambient notification icons be shown
409      * @param showLowPriority should icons from silent notifications be shown
410      * @param hideDismissed should dismissed icons be hidden
411      * @param hideRepliedMessages should messages that have been replied to be hidden
412      * @param hidePulsing should pulsing notifications be hidden
413      */
updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function, NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon)414     private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
415             NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
416             boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
417             boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) {
418         ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
419         // Filter out ambient notifications and notification children.
420         for (int i = 0; i < mNotificationEntries.size(); i++) {
421             NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
422             if (entry != null && entry.getRow() != null) {
423                 if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
424                         hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing,
425                         onlyShowCenteredIcon)) {
426                     StatusBarIconView iconView = function.apply(entry);
427                     if (iconView != null) {
428                         toShow.add(iconView);
429                     }
430                 }
431             }
432         }
433 
434         // In case we are changing the suppression of a group, the replacement shouldn't flicker
435         // and it should just be replaced instead. We therefore look for notifications that were
436         // just replaced by the child or vice-versa to suppress this.
437 
438         ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
439         ArrayList<View> toRemove = new ArrayList<>();
440         for (int i = 0; i < hostLayout.getChildCount(); i++) {
441             View child = hostLayout.getChildAt(i);
442             if (!(child instanceof StatusBarIconView)) {
443                 continue;
444             }
445             if (!toShow.contains(child)) {
446                 boolean iconWasReplaced = false;
447                 StatusBarIconView removedIcon = (StatusBarIconView) child;
448                 String removedGroupKey = removedIcon.getNotification().getGroupKey();
449                 for (int j = 0; j < toShow.size(); j++) {
450                     StatusBarIconView candidate = toShow.get(j);
451                     if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
452                             && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
453                         if (!iconWasReplaced) {
454                             iconWasReplaced = true;
455                         } else {
456                             iconWasReplaced = false;
457                             break;
458                         }
459                     }
460                 }
461                 if (iconWasReplaced) {
462                     ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
463                     if (statusBarIcons == null) {
464                         statusBarIcons = new ArrayList<>();
465                         replacingIcons.put(removedGroupKey, statusBarIcons);
466                     }
467                     statusBarIcons.add(removedIcon.getStatusBarIcon());
468                 }
469                 toRemove.add(removedIcon);
470             }
471         }
472         // removing all duplicates
473         ArrayList<String> duplicates = new ArrayList<>();
474         for (String key : replacingIcons.keySet()) {
475             ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
476             if (statusBarIcons.size() != 1) {
477                 duplicates.add(key);
478             }
479         }
480         replacingIcons.removeAll(duplicates);
481         hostLayout.setReplacingIcons(replacingIcons);
482 
483         final int toRemoveCount = toRemove.size();
484         for (int i = 0; i < toRemoveCount; i++) {
485             hostLayout.removeView(toRemove.get(i));
486         }
487 
488         final FrameLayout.LayoutParams params = generateIconLayoutParams();
489         for (int i = 0; i < toShow.size(); i++) {
490             StatusBarIconView v = toShow.get(i);
491             // The view might still be transiently added if it was just removed and added again
492             hostLayout.removeTransientView(v);
493             if (v.getParent() == null) {
494                 if (hideDismissed) {
495                     v.setOnDismissListener(mUpdateStatusBarIcons);
496                 }
497                 hostLayout.addView(v, i, params);
498             }
499         }
500 
501         hostLayout.setChangingViewPositions(true);
502         // Re-sort notification icons
503         final int childCount = hostLayout.getChildCount();
504         for (int i = 0; i < childCount; i++) {
505             View actual = hostLayout.getChildAt(i);
506             StatusBarIconView expected = toShow.get(i);
507             if (actual == expected) {
508                 continue;
509             }
510             hostLayout.removeView(expected);
511             hostLayout.addView(expected, i);
512         }
513         hostLayout.setChangingViewPositions(false);
514         hostLayout.setReplacingIcons(null);
515     }
516 
517     /**
518      * Applies {@link #mIconTint} to the notification icons.
519      * Applies {@link #mCenteredIconTint} to the center notification icon.
520      */
applyNotificationIconsTint()521     private void applyNotificationIconsTint() {
522         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
523             final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
524             if (iv.getWidth() != 0) {
525                 updateTintForIcon(iv, mIconTint);
526             } else {
527                 iv.executeOnLayout(() -> updateTintForIcon(iv, mIconTint));
528             }
529         }
530 
531         for (int i = 0; i < mCenteredIcon.getChildCount(); i++) {
532             final StatusBarIconView iv = (StatusBarIconView) mCenteredIcon.getChildAt(i);
533             if (iv.getWidth() != 0) {
534                 updateTintForIcon(iv, mCenteredIconTint);
535             } else {
536                 iv.executeOnLayout(() -> updateTintForIcon(iv, mCenteredIconTint));
537             }
538         }
539 
540         updateAodIconColors();
541     }
542 
updateTintForIcon(StatusBarIconView v, int tint)543     private void updateTintForIcon(StatusBarIconView v, int tint) {
544         boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
545         int color = StatusBarIconView.NO_COLOR;
546         boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
547         if (colorize) {
548             color = DarkIconDispatcher.getTint(mTintArea, v, tint);
549         }
550         v.setStaticDrawableColor(color);
551         v.setDecorColor(tint);
552     }
553 
554     /**
555      * Shows the icon view given in the center.
556      */
showIconCentered(NotificationEntry entry)557     public void showIconCentered(NotificationEntry entry) {
558         StatusBarIconView icon = entry == null ? null : entry.getIcons().getCenteredIcon();
559         if (!Objects.equals(mCenteredIconView, icon)) {
560             mCenteredIconView = icon;
561             updateNotificationIcons();
562         }
563     }
564 
showIconIsolated(StatusBarIconView icon, boolean animated)565     public void showIconIsolated(StatusBarIconView icon, boolean animated) {
566         mNotificationIcons.showIconIsolated(icon, animated);
567     }
568 
setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate)569     public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
570         mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
571     }
572 
573     @Override
onDozingChanged(boolean isDozing)574     public void onDozingChanged(boolean isDozing) {
575         if (mAodIcons == null) {
576             return;
577         }
578         boolean animate = mDozeParameters.getAlwaysOn()
579                 && !mDozeParameters.getDisplayNeedsBlanking();
580         mAodIcons.setDozing(isDozing, animate, 0);
581     }
582 
setAnimationsEnabled(boolean enabled)583     public void setAnimationsEnabled(boolean enabled) {
584         mAnimationsEnabled = enabled;
585         updateAnimations();
586     }
587 
588     @Override
onStateChanged(int newState)589     public void onStateChanged(int newState) {
590         updateAodIconsVisibility(false /* animate */, false /* force */);
591         updateAnimations();
592     }
593 
updateAnimations()594     private void updateAnimations() {
595         boolean inShade = mStatusBarStateController.getState() == StatusBarState.SHADE;
596         if (mAodIcons != null) {
597             mAodIcons.setAnimationsEnabled(mAnimationsEnabled && !inShade);
598         }
599         mCenteredIcon.setAnimationsEnabled(mAnimationsEnabled && inShade);
600         mNotificationIcons.setAnimationsEnabled(mAnimationsEnabled && inShade);
601     }
602 
onThemeChanged()603     public void onThemeChanged() {
604         reloadAodColor();
605         updateAodIconColors();
606     }
607 
getHeight()608     public int getHeight() {
609         return mAodIcons == null ? 0 : mAodIcons.getHeight();
610     }
611 
appearAodIcons()612     public void appearAodIcons() {
613         if (mAodIcons == null) {
614             return;
615         }
616         if (mDozeParameters.shouldControlScreenOff()) {
617             mAodIcons.setTranslationY(-mAodIconAppearTranslation);
618             mAodIcons.setAlpha(0);
619             animateInAodIconTranslation();
620             mAodIcons.animate()
621                     .alpha(1)
622                     .setInterpolator(Interpolators.LINEAR)
623                     .setDuration(AOD_ICONS_APPEAR_DURATION)
624                     .start();
625         } else {
626             mAodIcons.setAlpha(1.0f);
627             mAodIcons.setTranslationY(0);
628         }
629     }
630 
animateInAodIconTranslation()631     private void animateInAodIconTranslation() {
632         mAodIcons.animate()
633                 .setInterpolator(Interpolators.DECELERATE_QUINT)
634                 .translationY(0)
635                 .setDuration(AOD_ICONS_APPEAR_DURATION)
636                 .start();
637     }
638 
reloadAodColor()639     private void reloadAodColor() {
640         mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
641                 R.attr.wallpaperTextColor);
642     }
643 
updateAodIconColors()644     private void updateAodIconColors() {
645         if (mAodIcons != null) {
646             for (int i = 0; i < mAodIcons.getChildCount(); i++) {
647                 final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
648                 if (iv.getWidth() != 0) {
649                     updateTintForIcon(iv, mAodIconTint);
650                 } else {
651                     iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
652                 }
653             }
654         }
655     }
656 
657     @Override
onFullyHiddenChanged(boolean fullyHidden)658     public void onFullyHiddenChanged(boolean fullyHidden) {
659         boolean animate = true;
660         if (!mBypassController.getBypassEnabled()) {
661             animate = mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking();
662             // We only want the appear animations to happen when the notifications get fully hidden,
663             // since otherwise the unhide animation overlaps
664             animate &= fullyHidden;
665         }
666         updateAodIconsVisibility(animate, false /* force */);
667         updateAodNotificationIcons();
668     }
669 
670     @Override
onPulseExpansionChanged(boolean expandingChanged)671     public void onPulseExpansionChanged(boolean expandingChanged) {
672         if (expandingChanged) {
673             updateAodIconsVisibility(true /* animate */, false /* force */);
674         }
675     }
676 
updateAodIconsVisibility(boolean animate, boolean forceUpdate)677     private void updateAodIconsVisibility(boolean animate, boolean forceUpdate) {
678         if (mAodIcons == null) {
679             return;
680         }
681         boolean visible = mBypassController.getBypassEnabled()
682                 || mWakeUpCoordinator.getNotificationsFullyHidden();
683 
684         // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
685         // playing, in which case we want them to be visible since we're animating in the AOD UI and
686         // will be switching to KEYGUARD shortly.
687         if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD
688                 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
689             visible = false;
690         }
691         if (visible && mWakeUpCoordinator.isPulseExpanding()
692                 && !mBypassController.getBypassEnabled()) {
693             visible = false;
694         }
695         if (mAodIconsVisible != visible || forceUpdate) {
696             mAodIconsVisible = visible;
697             mAodIcons.animate().cancel();
698             if (animate) {
699                 boolean wasFullyInvisible = mAodIcons.getVisibility() != View.VISIBLE;
700                 if (mAodIconsVisible) {
701                     if (wasFullyInvisible) {
702                         // No fading here, let's just appear the icons instead!
703                         mAodIcons.setVisibility(View.VISIBLE);
704                         mAodIcons.setAlpha(1.0f);
705                         appearAodIcons();
706                     } else {
707                         // Let's make sure the icon are translated to 0, since we cancelled it above
708                         animateInAodIconTranslation();
709                         // We were fading out, let's fade in instead
710                         CrossFadeHelper.fadeIn(mAodIcons);
711                     }
712                 } else {
713                     // Let's make sure the icon are translated to 0, since we cancelled it above
714                     animateInAodIconTranslation();
715                     CrossFadeHelper.fadeOut(mAodIcons);
716                 }
717             } else {
718                 mAodIcons.setAlpha(1.0f);
719                 mAodIcons.setTranslationY(0);
720                 mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
721             }
722         }
723     }
724 
725     @Override
demoCommands()726     public List<String> demoCommands() {
727         ArrayList<String> commands = new ArrayList<>();
728         commands.add(DemoMode.COMMAND_NOTIFICATIONS);
729         return commands;
730     }
731 
732     @Override
dispatchDemoCommand(String command, Bundle args)733     public void dispatchDemoCommand(String command, Bundle args) {
734         if (mNotificationIconArea != null) {
735             String visible = args.getString("visible");
736             int vis = "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
737             mNotificationIconArea.setVisibility(vis);
738         }
739     }
740 
741     @Override
onDemoModeFinished()742     public void onDemoModeFinished() {
743         if (mNotificationIconArea != null) {
744             mNotificationIconArea.setVisibility(View.VISIBLE);
745         }
746     }
747 }
748