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