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