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.graphics.drawable.Icon;
8 import android.support.annotation.NonNull;
9 import android.support.v4.util.ArrayMap;
10 import android.support.v4.util.ArraySet;
11 import android.view.LayoutInflater;
12 import android.view.View;
13 import android.widget.FrameLayout;
14 
15 import com.android.internal.statusbar.StatusBarIcon;
16 import com.android.internal.util.NotificationColorUtil;
17 import com.android.systemui.R;
18 import com.android.systemui.statusbar.ExpandableNotificationRow;
19 import com.android.systemui.statusbar.NotificationData;
20 import com.android.systemui.statusbar.NotificationShelf;
21 import com.android.systemui.statusbar.StatusBarIconView;
22 import com.android.systemui.statusbar.notification.NotificationUtils;
23 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
24 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
25 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
26 
27 import java.util.ArrayList;
28 import java.util.function.Function;
29 
30 /**
31  * A controller for the space in the status bar to the left of the system icons. This area is
32  * normally reserved for notifications.
33  */
34 public class NotificationIconAreaController implements DarkReceiver {
35     private final NotificationColorUtil mNotificationColorUtil;
36 
37     private int mIconSize;
38     private int mIconHPadding;
39     private int mIconTint = Color.WHITE;
40 
41     private StatusBar mStatusBar;
42     protected View mNotificationIconArea;
43     private NotificationIconContainer mNotificationIcons;
44     private NotificationIconContainer mShelfIcons;
45     private final Rect mTintArea = new Rect();
46     private NotificationStackScrollLayout mNotificationScrollLayout;
47     private Context mContext;
48 
NotificationIconAreaController(Context context, StatusBar statusBar)49     public NotificationIconAreaController(Context context, StatusBar statusBar) {
50         mStatusBar = statusBar;
51         mNotificationColorUtil = NotificationColorUtil.getInstance(context);
52         mContext = context;
53 
54         initializeNotificationAreaViews(context);
55     }
56 
inflateIconArea(LayoutInflater inflater)57     protected View inflateIconArea(LayoutInflater inflater) {
58         return inflater.inflate(R.layout.notification_icon_area, null);
59     }
60 
61     /**
62      * Initializes the views that will represent the notification area.
63      */
initializeNotificationAreaViews(Context context)64     protected void initializeNotificationAreaViews(Context context) {
65         reloadDimens(context);
66 
67         LayoutInflater layoutInflater = LayoutInflater.from(context);
68         mNotificationIconArea = inflateIconArea(layoutInflater);
69         mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
70                 R.id.notificationIcons);
71 
72         mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
73     }
74 
setupShelf(NotificationShelf shelf)75     public void setupShelf(NotificationShelf shelf) {
76         mShelfIcons = shelf.getShelfIcons();
77         shelf.setCollapsedIcons(mNotificationIcons);
78     }
79 
onDensityOrFontScaleChanged(Context context)80     public void onDensityOrFontScaleChanged(Context context) {
81         reloadDimens(context);
82         final FrameLayout.LayoutParams params = generateIconLayoutParams();
83         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
84             View child = mNotificationIcons.getChildAt(i);
85             child.setLayoutParams(params);
86         }
87         for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
88             View child = mShelfIcons.getChildAt(i);
89             child.setLayoutParams(params);
90         }
91     }
92 
93     @NonNull
generateIconLayoutParams()94     private FrameLayout.LayoutParams generateIconLayoutParams() {
95         return new FrameLayout.LayoutParams(
96                 mIconSize + 2 * mIconHPadding, getHeight());
97     }
98 
reloadDimens(Context context)99     private void reloadDimens(Context context) {
100         Resources res = context.getResources();
101         mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
102         mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
103     }
104 
105     /**
106      * Returns the view that represents the notification area.
107      */
getNotificationInnerAreaView()108     public View getNotificationInnerAreaView() {
109         return mNotificationIconArea;
110     }
111 
112     /**
113      * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
114      * Sets the color that should be used to tint any icons in the notification area.
115      *
116      * @param tintArea the area in which to tint the icons, specified in screen coordinates
117      * @param darkIntensity
118      */
onDarkChanged(Rect tintArea, float darkIntensity, int iconTint)119     public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
120         if (tintArea == null) {
121             mTintArea.setEmpty();
122         } else {
123             mTintArea.set(tintArea);
124         }
125         mIconTint = iconTint;
126         applyNotificationIconsTint();
127     }
128 
getHeight()129     protected int getHeight() {
130         return mStatusBar.getStatusBarHeight();
131     }
132 
shouldShowNotificationIcon(NotificationData.Entry entry, NotificationData notificationData, boolean showAmbient)133     protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
134             NotificationData notificationData, boolean showAmbient) {
135         if (notificationData.isAmbient(entry.key) && !showAmbient) {
136             return false;
137         }
138         if (!StatusBar.isTopLevelChild(entry)) {
139             return false;
140         }
141         if (entry.row.getVisibility() == View.GONE) {
142             return false;
143         }
144 
145         return true;
146     }
147 
148     /**
149      * Updates the notifications with the given list of notifications to display.
150      */
updateNotificationIcons(NotificationData notificationData)151     public void updateNotificationIcons(NotificationData notificationData) {
152 
153         updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons,
154                 false /* showAmbient */);
155         updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons,
156                 NotificationShelf.SHOW_AMBIENT_ICONS);
157 
158         applyNotificationIconsTint();
159     }
160 
161     /**
162      * Updates the notification icons for a host layout. This will ensure that the notification
163      * host layout will have the same icons like the ones in here.
164      *
165      * @param notificationData the notification data to look up which notifications are relevant
166      * @param function A function to look up an icon view based on an entry
167      * @param hostLayout which layout should be updated
168      * @param showAmbient should ambient notification icons be shown
169      */
updateIconsForLayout(NotificationData notificationData, Function<NotificationData.Entry, StatusBarIconView> function, NotificationIconContainer hostLayout, boolean showAmbient)170     private void updateIconsForLayout(NotificationData notificationData,
171             Function<NotificationData.Entry, StatusBarIconView> function,
172             NotificationIconContainer hostLayout, boolean showAmbient) {
173         ArrayList<StatusBarIconView> toShow = new ArrayList<>(
174                 mNotificationScrollLayout.getChildCount());
175 
176         // Filter out ambient notifications and notification children.
177         for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
178             View view = mNotificationScrollLayout.getChildAt(i);
179             if (view instanceof ExpandableNotificationRow) {
180                 NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
181                 if (shouldShowNotificationIcon(ent, notificationData, showAmbient)) {
182                     toShow.add(function.apply(ent));
183                 }
184             }
185         }
186 
187         // In case we are changing the suppression of a group, the replacement shouldn't flicker
188         // and it should just be replaced instead. We therefore look for notifications that were
189         // just replaced by the child or vice-versa to suppress this.
190 
191         ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
192         ArrayList<View> toRemove = new ArrayList<>();
193         for (int i = 0; i < hostLayout.getChildCount(); i++) {
194             View child = hostLayout.getChildAt(i);
195             if (!(child instanceof StatusBarIconView)) {
196                 continue;
197             }
198             if (!toShow.contains(child)) {
199                 boolean iconWasReplaced = false;
200                 StatusBarIconView removedIcon = (StatusBarIconView) child;
201                 String removedGroupKey = removedIcon.getNotification().getGroupKey();
202                 for (int j = 0; j < toShow.size(); j++) {
203                     StatusBarIconView candidate = toShow.get(j);
204                     if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
205                             && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
206                         if (!iconWasReplaced) {
207                             iconWasReplaced = true;
208                         } else {
209                             iconWasReplaced = false;
210                             break;
211                         }
212                     }
213                 }
214                 if (iconWasReplaced) {
215                     ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
216                     if (statusBarIcons == null) {
217                         statusBarIcons = new ArrayList<>();
218                         replacingIcons.put(removedGroupKey, statusBarIcons);
219                     }
220                     statusBarIcons.add(removedIcon.getStatusBarIcon());
221                 }
222                 toRemove.add(removedIcon);
223             }
224         }
225         // removing all duplicates
226         ArrayList<String> duplicates = new ArrayList<>();
227         for (String key : replacingIcons.keySet()) {
228             ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
229             if (statusBarIcons.size() != 1) {
230                 duplicates.add(key);
231             }
232         }
233         replacingIcons.removeAll(duplicates);
234         hostLayout.setReplacingIcons(replacingIcons);
235 
236         final int toRemoveCount = toRemove.size();
237         for (int i = 0; i < toRemoveCount; i++) {
238             hostLayout.removeView(toRemove.get(i));
239         }
240 
241         final FrameLayout.LayoutParams params = generateIconLayoutParams();
242         for (int i = 0; i < toShow.size(); i++) {
243             View v = toShow.get(i);
244             // The view might still be transiently added if it was just removed and added again
245             hostLayout.removeTransientView(v);
246             if (v.getParent() == null) {
247                 hostLayout.addView(v, i, params);
248             }
249         }
250 
251         hostLayout.setChangingViewPositions(true);
252         // Re-sort notification icons
253         final int childCount = hostLayout.getChildCount();
254         for (int i = 0; i < childCount; i++) {
255             View actual = hostLayout.getChildAt(i);
256             StatusBarIconView expected = toShow.get(i);
257             if (actual == expected) {
258                 continue;
259             }
260             hostLayout.removeView(expected);
261             hostLayout.addView(expected, i);
262         }
263         hostLayout.setChangingViewPositions(false);
264         hostLayout.setReplacingIcons(null);
265     }
266 
267     /**
268      * Applies {@link #mIconTint} to the notification icons.
269      */
applyNotificationIconsTint()270     private void applyNotificationIconsTint() {
271         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
272             StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
273             boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
274             int color = StatusBarIconView.NO_COLOR;
275             boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
276             if (colorize) {
277                 color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
278             }
279             v.setStaticDrawableColor(color);
280             v.setDecorColor(mIconTint);
281         }
282     }
283 }
284