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