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