• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.statusbar.phone;
2 
3 import android.content.Context;
4 import android.content.res.Resources;
5 import android.graphics.Color;
6 import android.graphics.Rect;
7 import android.view.LayoutInflater;
8 import android.view.View;
9 import android.view.ViewGroup;
10 import android.widget.FrameLayout;
11 
12 import androidx.annotation.NonNull;
13 import androidx.collection.ArrayMap;
14 
15 import com.android.internal.annotations.VisibleForTesting;
16 import com.android.internal.statusbar.StatusBarIcon;
17 import com.android.internal.util.ContrastColorUtil;
18 import com.android.systemui.Dependency;
19 import com.android.systemui.R;
20 import com.android.systemui.plugins.DarkIconDispatcher;
21 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
22 import com.android.systemui.plugins.statusbar.StatusBarStateController;
23 import com.android.systemui.statusbar.NotificationListener;
24 import com.android.systemui.statusbar.NotificationMediaManager;
25 import com.android.systemui.statusbar.NotificationShelf;
26 import com.android.systemui.statusbar.StatusBarIconView;
27 import com.android.systemui.statusbar.StatusBarState;
28 import com.android.systemui.statusbar.notification.NotificationEntryManager;
29 import com.android.systemui.statusbar.notification.NotificationUtils;
30 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
31 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
32 
33 import java.util.ArrayList;
34 import java.util.Objects;
35 import java.util.function.Function;
36 
37 /**
38  * A controller for the space in the status bar to the left of the system icons. This area is
39  * normally reserved for notifications.
40  */
41 public class NotificationIconAreaController implements DarkReceiver,
42         StatusBarStateController.StateListener {
43 
44     public static final String HIGH_PRIORITY = "high_priority";
45 
46     private final ContrastColorUtil mContrastColorUtil;
47     private final NotificationEntryManager mEntryManager;
48     private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
49     private final StatusBarStateController mStatusBarStateController;
50     private final NotificationMediaManager mMediaManager;
51 
52     private int mIconSize;
53     private int mIconHPadding;
54     private int mIconTint = Color.WHITE;
55     private int mCenteredIconTint = Color.WHITE;
56 
57     private StatusBar mStatusBar;
58     protected View mNotificationIconArea;
59     private NotificationIconContainer mNotificationIcons;
60     private NotificationIconContainer mShelfIcons;
61     protected View mCenteredIconArea;
62     private NotificationIconContainer mCenteredIcon;
63     private StatusBarIconView mCenteredIconView;
64     private final Rect mTintArea = new Rect();
65     private ViewGroup mNotificationScrollLayout;
66     private Context mContext;
67     private boolean mFullyDark;
68     private boolean mAnimationsEnabled;
69 
70     /**
71      * Ratio representing being awake or in ambient mode, where 1 is dark and 0 awake.
72      */
73     private float mDarkAmount;
74 
NotificationIconAreaController(Context context, StatusBar statusBar, StatusBarStateController statusBarStateController, NotificationMediaManager notificationMediaManager)75     public NotificationIconAreaController(Context context, StatusBar statusBar,
76             StatusBarStateController statusBarStateController,
77             NotificationMediaManager notificationMediaManager) {
78         mStatusBar = statusBar;
79         mContrastColorUtil = ContrastColorUtil.getInstance(context);
80         mContext = context;
81         mEntryManager = Dependency.get(NotificationEntryManager.class);
82         mStatusBarStateController = statusBarStateController;
83         mStatusBarStateController.addCallback(this);
84         mMediaManager = notificationMediaManager;
85 
86         initializeNotificationAreaViews(context);
87     }
88 
inflateIconArea(LayoutInflater inflater)89     protected View inflateIconArea(LayoutInflater inflater) {
90         return inflater.inflate(R.layout.notification_icon_area, null);
91     }
92 
93     /**
94      * Initializes the views that will represent the notification area.
95      */
initializeNotificationAreaViews(Context context)96     protected void initializeNotificationAreaViews(Context context) {
97         reloadDimens(context);
98 
99         LayoutInflater layoutInflater = LayoutInflater.from(context);
100         mNotificationIconArea = inflateIconArea(layoutInflater);
101         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
102 
103         mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
104 
105         mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);
106         mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);
107     }
108 
setupShelf(NotificationShelf shelf)109     public void setupShelf(NotificationShelf shelf) {
110         mShelfIcons = shelf.getShelfIcons();
111         shelf.setCollapsedIcons(mNotificationIcons);
112     }
113 
onDensityOrFontScaleChanged(Context context)114     public void onDensityOrFontScaleChanged(Context context) {
115         reloadDimens(context);
116         final FrameLayout.LayoutParams params = generateIconLayoutParams();
117         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
118             View child = mNotificationIcons.getChildAt(i);
119             child.setLayoutParams(params);
120         }
121         for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
122             View child = mShelfIcons.getChildAt(i);
123             child.setLayoutParams(params);
124         }
125         for (int i = 0; i < mCenteredIcon.getChildCount(); i++) {
126             View child = mCenteredIcon.getChildAt(i);
127             child.setLayoutParams(params);
128         }
129     }
130 
131     @NonNull
generateIconLayoutParams()132     private FrameLayout.LayoutParams generateIconLayoutParams() {
133         return new FrameLayout.LayoutParams(
134                 mIconSize + 2 * mIconHPadding, getHeight());
135     }
136 
reloadDimens(Context context)137     private void reloadDimens(Context context) {
138         Resources res = context.getResources();
139         mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
140         mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
141     }
142 
143     /**
144      * Returns the view that represents the notification area.
145      */
getNotificationInnerAreaView()146     public View getNotificationInnerAreaView() {
147         return mNotificationIconArea;
148     }
149 
150     /**
151      * Returns the view that represents the centered notification area.
152      */
getCenteredNotificationAreaView()153     public View getCenteredNotificationAreaView() {
154         return mCenteredIconArea;
155     }
156 
157     /**
158      * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
159      * Sets the color that should be used to tint any icons in the notification area.
160      *
161      * @param tintArea the area in which to tint the icons, specified in screen coordinates
162      * @param darkIntensity
163      */
onDarkChanged(Rect tintArea, float darkIntensity, int iconTint)164     public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
165         if (tintArea == null) {
166             mTintArea.setEmpty();
167         } else {
168             mTintArea.set(tintArea);
169         }
170 
171         if (mNotificationIconArea != null) {
172             if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
173                 mIconTint = iconTint;
174             }
175         } else {
176             mIconTint = iconTint;
177         }
178 
179         if (mCenteredIconArea != null) {
180             if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
181                 mCenteredIconTint = iconTint;
182             }
183         } else {
184             mCenteredIconTint = iconTint;
185         }
186 
187         applyNotificationIconsTint();
188     }
189 
getHeight()190     protected int getHeight() {
191         return mStatusBar.getStatusBarHeight();
192     }
193 
shouldShowNotificationIcon(NotificationEntry entry, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon)194     protected boolean shouldShowNotificationIcon(NotificationEntry entry,
195             boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
196             boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon) {
197 
198         final boolean isCenteredNotificationIcon = entry.centeredIcon != null
199                 && Objects.equals(entry.centeredIcon, mCenteredIconView);
200         if (hideCenteredIcon == isCenteredNotificationIcon) {
201             return false;
202         }
203         if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
204             return false;
205         }
206         if (hideCurrentMedia && entry.key.equals(mMediaManager.getMediaNotificationKey())) {
207             return false;
208         }
209         if (!showLowPriority && !entry.isHighPriority()) {
210             return false;
211         }
212         if (!entry.isTopLevelChild()) {
213             return false;
214         }
215         if (entry.getRow().getVisibility() == View.GONE) {
216             return false;
217         }
218         if (entry.isRowDismissed() && hideDismissed) {
219             return false;
220         }
221         if (hideRepliedMessages && entry.isLastMessageFromReply()) {
222             return false;
223         }
224         // showAmbient == show in shade but not shelf
225         if ((!showAmbient || mFullyDark) && entry.shouldSuppressStatusBar()) {
226             return false;
227         }
228         return true;
229     }
230 
231     /**
232      * Updates the notifications with the given list of notifications to display.
233      */
updateNotificationIcons()234     public void updateNotificationIcons() {
235         updateStatusBarIcons();
236         updateShelfIcons();
237         updateCenterIcon();
238 
239         applyNotificationIconsTint();
240     }
241 
updateShelfIcons()242     private void updateShelfIcons() {
243         updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
244                 true /* showAmbient */,
245                 true /* showLowPriority */,
246                 false /* hideDismissed */,
247                 mFullyDark /* hideRepliedMessages */,
248                 mFullyDark /* hideCurrentMedia */,
249                 true /* hide centered icon */);
250     }
251 
updateStatusBarIcons()252     public void updateStatusBarIcons() {
253         updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
254                 false /* showAmbient */,
255                 true /* showLowPriority */,
256                 true /* hideDismissed */,
257                 true /* hideRepliedMessages */,
258                 false /* hideCurrentMedia */,
259                 true /* hide centered icon */);
260     }
261 
updateCenterIcon()262     private void updateCenterIcon() {
263         updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon,
264                 false /* showAmbient */,
265                 true /* showLowPriority */,
266                 false /* hideDismissed */,
267                 false /* hideRepliedMessages */,
268                 mFullyDark /* hideCurrentMedia */,
269                 false /* hide centered icon */);
270     }
271 
272     /**
273      * If icons of the status bar should animate when they are added or removed.
274      */
setAnimationsEnabled(boolean enabled)275     public void setAnimationsEnabled(boolean enabled) {
276         mAnimationsEnabled = enabled;
277         updateAnimations();
278     }
279 
280     @Override
onStateChanged(int newState)281     public void onStateChanged(int newState) {
282         updateAnimations();
283     }
284 
updateAnimations()285     private void updateAnimations() {
286         boolean inShade = mStatusBarStateController.getState() == StatusBarState.SHADE;
287         mCenteredIcon.setAnimationsEnabled(mAnimationsEnabled && inShade);
288         mNotificationIcons.setAnimationsEnabled(mAnimationsEnabled && inShade);
289     }
290 
291     /**
292      * Updates the notification icons for a host layout. This will ensure that the notification
293      * host layout will have the same icons like the ones in here.
294      * @param function A function to look up an icon view based on an entry
295      * @param hostLayout which layout should be updated
296      * @param showAmbient should ambient notification icons be shown
297      * @param hideDismissed should dismissed icons be hidden
298      * @param hideRepliedMessages should messages that have been replied to be hidden
299      */
updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function, NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon)300     private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
301             NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
302             boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
303             boolean hideCenteredIcon) {
304         ArrayList<StatusBarIconView> toShow = new ArrayList<>(
305                 mNotificationScrollLayout.getChildCount());
306 
307         // Filter out ambient notifications and notification children.
308         for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
309             View view = mNotificationScrollLayout.getChildAt(i);
310             if (view instanceof ExpandableNotificationRow) {
311                 NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
312                 if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
313                         hideRepliedMessages, hideCurrentMedia, hideCenteredIcon)) {
314                     StatusBarIconView iconView = function.apply(ent);
315                     if (iconView != null) {
316                         toShow.add(iconView);
317                     }
318                 }
319             }
320         }
321 
322         // In case we are changing the suppression of a group, the replacement shouldn't flicker
323         // and it should just be replaced instead. We therefore look for notifications that were
324         // just replaced by the child or vice-versa to suppress this.
325 
326         ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
327         ArrayList<View> toRemove = new ArrayList<>();
328         for (int i = 0; i < hostLayout.getChildCount(); i++) {
329             View child = hostLayout.getChildAt(i);
330             if (!(child instanceof StatusBarIconView)) {
331                 continue;
332             }
333             if (!toShow.contains(child)) {
334                 boolean iconWasReplaced = false;
335                 StatusBarIconView removedIcon = (StatusBarIconView) child;
336                 String removedGroupKey = removedIcon.getNotification().getGroupKey();
337                 for (int j = 0; j < toShow.size(); j++) {
338                     StatusBarIconView candidate = toShow.get(j);
339                     if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
340                             && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
341                         if (!iconWasReplaced) {
342                             iconWasReplaced = true;
343                         } else {
344                             iconWasReplaced = false;
345                             break;
346                         }
347                     }
348                 }
349                 if (iconWasReplaced) {
350                     ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
351                     if (statusBarIcons == null) {
352                         statusBarIcons = new ArrayList<>();
353                         replacingIcons.put(removedGroupKey, statusBarIcons);
354                     }
355                     statusBarIcons.add(removedIcon.getStatusBarIcon());
356                 }
357                 toRemove.add(removedIcon);
358             }
359         }
360         // removing all duplicates
361         ArrayList<String> duplicates = new ArrayList<>();
362         for (String key : replacingIcons.keySet()) {
363             ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
364             if (statusBarIcons.size() != 1) {
365                 duplicates.add(key);
366             }
367         }
368         replacingIcons.removeAll(duplicates);
369         hostLayout.setReplacingIcons(replacingIcons);
370 
371         final int toRemoveCount = toRemove.size();
372         for (int i = 0; i < toRemoveCount; i++) {
373             hostLayout.removeView(toRemove.get(i));
374         }
375 
376         final FrameLayout.LayoutParams params = generateIconLayoutParams();
377         for (int i = 0; i < toShow.size(); i++) {
378             StatusBarIconView v = toShow.get(i);
379             // The view might still be transiently added if it was just removed and added again
380             hostLayout.removeTransientView(v);
381             if (v.getParent() == null) {
382                 if (hideDismissed) {
383                     v.setOnDismissListener(mUpdateStatusBarIcons);
384                 }
385                 hostLayout.addView(v, i, params);
386             }
387         }
388 
389         hostLayout.setChangingViewPositions(true);
390         // Re-sort notification icons
391         final int childCount = hostLayout.getChildCount();
392         for (int i = 0; i < childCount; i++) {
393             View actual = hostLayout.getChildAt(i);
394             StatusBarIconView expected = toShow.get(i);
395             if (actual == expected) {
396                 continue;
397             }
398             hostLayout.removeView(expected);
399             hostLayout.addView(expected, i);
400         }
401         hostLayout.setChangingViewPositions(false);
402         hostLayout.setReplacingIcons(null);
403     }
404 
405     /**
406      * Applies {@link #mIconTint} to the notification icons.
407      * Applies {@link #mCenteredIconTint} to the center notification icon.
408      */
applyNotificationIconsTint()409     private void applyNotificationIconsTint() {
410         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
411             final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
412             if (iv.getWidth() != 0) {
413                 updateTintForIcon(iv, mIconTint);
414             } else {
415                 iv.executeOnLayout(() -> updateTintForIcon(iv, mIconTint));
416             }
417         }
418 
419         for (int i = 0; i < mCenteredIcon.getChildCount(); i++) {
420             final StatusBarIconView iv = (StatusBarIconView) mCenteredIcon.getChildAt(i);
421             if (iv.getWidth() != 0) {
422                 updateTintForIcon(iv, mCenteredIconTint);
423             } else {
424                 iv.executeOnLayout(() -> updateTintForIcon(iv, mCenteredIconTint));
425             }
426         }
427     }
428 
updateTintForIcon(StatusBarIconView v, int tint)429     private void updateTintForIcon(StatusBarIconView v, int tint) {
430         boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
431         int color = StatusBarIconView.NO_COLOR;
432         boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
433         if (colorize) {
434             color = DarkIconDispatcher.getTint(mTintArea, v, tint);
435         }
436         v.setStaticDrawableColor(color);
437         v.setDecorColor(tint);
438     }
439 
setDark(boolean dark)440     public void setDark(boolean dark) {
441         mNotificationIcons.setDark(dark, false, 0);
442         mShelfIcons.setDark(dark, false, 0);
443         mCenteredIcon.setDark(dark, false, 0);
444     }
445 
446     /**
447      * Shows the icon view given in the center.
448      */
showIconCentered(NotificationEntry entry)449     public void showIconCentered(NotificationEntry entry) {
450         StatusBarIconView icon = entry == null ? null :  entry.centeredIcon;
451         if (!Objects.equals(mCenteredIconView, icon)) {
452             mCenteredIconView = icon;
453             updateNotificationIcons();
454         }
455     }
456 
showIconIsolated(StatusBarIconView icon, boolean animated)457     public void showIconIsolated(StatusBarIconView icon, boolean animated) {
458         mNotificationIcons.showIconIsolated(icon, animated);
459     }
460 
setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate)461     public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
462         mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
463     }
464 
465     @Override
onDozeAmountChanged(float linear, float eased)466     public void onDozeAmountChanged(float linear, float eased) {
467         mDarkAmount = linear;
468         boolean fullyDark = mDarkAmount == 1f;
469         if (mFullyDark != fullyDark) {
470             mFullyDark = fullyDark;
471             updateShelfIcons();
472         }
473     }
474 }
475