blob: 2bfdfebae82d3f8196d87e37eda9fc1cb4079a1e [file] [log] [blame]
Anthony Chen83092c62016-01-11 17:00:36 -08001package com.android.systemui.statusbar.phone;
2
3import android.content.Context;
Anthony Chen83092c62016-01-11 17:00:36 -08004import android.content.res.Resources;
5import android.graphics.Color;
Jorim Jaggi86905582016-02-09 21:36:09 -08006import android.graphics.Rect;
Selim Cinek3e7592d2016-04-11 09:35:54 +08007import android.support.annotation.NonNull;
Selim Cinek72fc8db2017-06-06 18:07:47 -07008import android.support.v4.util.ArrayMap;
Anthony Chen83092c62016-01-11 17:00:36 -08009import android.view.LayoutInflater;
10import android.view.View;
Selim Cinek0e8d77e2016-11-29 10:35:42 -080011import android.widget.FrameLayout;
Winsonc0d70582016-01-29 10:24:39 -080012
Selim Cinek72fc8db2017-06-06 18:07:47 -070013import com.android.internal.statusbar.StatusBarIcon;
Anthony Chen83092c62016-01-11 17:00:36 -080014import com.android.internal.util.NotificationColorUtil;
Selim Cinekd03518c2018-03-15 12:13:51 -070015import com.android.systemui.Dependency;
Anthony Chen83092c62016-01-11 17:00:36 -080016import com.android.systemui.R;
Selim Cinek281c2022016-10-13 19:14:43 -070017import com.android.systemui.statusbar.ExpandableNotificationRow;
Anthony Chen83092c62016-01-11 17:00:36 -080018import com.android.systemui.statusbar.NotificationData;
Selim Cinekd03518c2018-03-15 12:13:51 -070019import com.android.systemui.statusbar.NotificationEntryManager;
Selim Cinek281c2022016-10-13 19:14:43 -070020import com.android.systemui.statusbar.NotificationShelf;
Anthony Chen83092c62016-01-11 17:00:36 -080021import com.android.systemui.statusbar.StatusBarIconView;
22import com.android.systemui.statusbar.notification.NotificationUtils;
Jason Monkaa573e92017-01-27 17:00:29 -050023import com.android.systemui.statusbar.policy.DarkIconDispatcher;
24import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
Selim Cinek281c2022016-10-13 19:14:43 -070025import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Anthony Chen83092c62016-01-11 17:00:36 -080026
27import java.util.ArrayList;
Selim Cinek281c2022016-10-13 19:14:43 -070028import java.util.function.Function;
Anthony Chen83092c62016-01-11 17:00:36 -080029
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 */
Jason Monkaa573e92017-01-27 17:00:29 -050034public class NotificationIconAreaController implements DarkReceiver {
Anthony Chen83092c62016-01-11 17:00:36 -080035 private final NotificationColorUtil mNotificationColorUtil;
Selim Cinekd03518c2018-03-15 12:13:51 -070036 private final NotificationEntryManager mEntryManager;
37 private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
Anthony Chen83092c62016-01-11 17:00:36 -080038
39 private int mIconSize;
40 private int mIconHPadding;
41 private int mIconTint = Color.WHITE;
42
Jason Monk2a6ea9c2017-01-26 11:14:51 -050043 private StatusBar mStatusBar;
Anthony Chen83092c62016-01-11 17:00:36 -080044 protected View mNotificationIconArea;
Selim Cinek281c2022016-10-13 19:14:43 -070045 private NotificationIconContainer mNotificationIcons;
Selim Cinek5b5beb012016-11-08 18:11:58 -080046 private NotificationIconContainer mShelfIcons;
Jorim Jaggi86905582016-02-09 21:36:09 -080047 private final Rect mTintArea = new Rect();
Selim Cinek281c2022016-10-13 19:14:43 -070048 private NotificationStackScrollLayout mNotificationScrollLayout;
49 private Context mContext;
Selim Cinek9bfc7a52018-06-11 16:09:00 -070050 private boolean mFullyDark;
51 private boolean mHasShelfIconsWhenFullyDark;
Anthony Chen83092c62016-01-11 17:00:36 -080052
Jason Monk2a6ea9c2017-01-26 11:14:51 -050053 public NotificationIconAreaController(Context context, StatusBar statusBar) {
54 mStatusBar = statusBar;
Anthony Chen83092c62016-01-11 17:00:36 -080055 mNotificationColorUtil = NotificationColorUtil.getInstance(context);
Selim Cinek281c2022016-10-13 19:14:43 -070056 mContext = context;
Selim Cinekd03518c2018-03-15 12:13:51 -070057 mEntryManager = Dependency.get(NotificationEntryManager.class);
Anthony Chen83092c62016-01-11 17:00:36 -080058
59 initializeNotificationAreaViews(context);
60 }
61
Xiaohui Cheneb04a992016-03-22 14:58:03 -070062 protected View inflateIconArea(LayoutInflater inflater) {
63 return inflater.inflate(R.layout.notification_icon_area, null);
64 }
65
Anthony Chen83092c62016-01-11 17:00:36 -080066 /**
67 * Initializes the views that will represent the notification area.
68 */
69 protected void initializeNotificationAreaViews(Context context) {
Selim Cinek3e7592d2016-04-11 09:35:54 +080070 reloadDimens(context);
Anthony Chen83092c62016-01-11 17:00:36 -080071
72 LayoutInflater layoutInflater = LayoutInflater.from(context);
Xiaohui Cheneb04a992016-03-22 14:58:03 -070073 mNotificationIconArea = inflateIconArea(layoutInflater);
Selim Cinek281c2022016-10-13 19:14:43 -070074 mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
75 R.id.notificationIcons);
Anthony Chen83092c62016-01-11 17:00:36 -080076
Jason Monkaa573e92017-01-27 17:00:29 -050077 mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
78 }
79
80 public void setupShelf(NotificationShelf shelf) {
Selim Cinek5b5beb012016-11-08 18:11:58 -080081 mShelfIcons = shelf.getShelfIcons();
Selim Cinek49014f82016-11-04 14:55:30 -070082 shelf.setCollapsedIcons(mNotificationIcons);
Anthony Chen83092c62016-01-11 17:00:36 -080083 }
84
Selim Cinek3e7592d2016-04-11 09:35:54 +080085 public void onDensityOrFontScaleChanged(Context context) {
86 reloadDimens(context);
Selim Cinek0e8d77e2016-11-29 10:35:42 -080087 final FrameLayout.LayoutParams params = generateIconLayoutParams();
Selim Cinek3e7592d2016-04-11 09:35:54 +080088 for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
89 View child = mNotificationIcons.getChildAt(i);
90 child.setLayoutParams(params);
Selim Cinek9ef119c2017-03-01 15:13:36 -080091 }
92 for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
93 View child = mShelfIcons.getChildAt(i);
Selim Cinek0e8d77e2016-11-29 10:35:42 -080094 child.setLayoutParams(params);
Selim Cinek3e7592d2016-04-11 09:35:54 +080095 }
96 }
97
98 @NonNull
Selim Cinek0e8d77e2016-11-29 10:35:42 -080099 private FrameLayout.LayoutParams generateIconLayoutParams() {
100 return new FrameLayout.LayoutParams(
Selim Cinek3e7592d2016-04-11 09:35:54 +0800101 mIconSize + 2 * mIconHPadding, getHeight());
102 }
103
104 private void reloadDimens(Context context) {
105 Resources res = context.getResources();
106 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
107 mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
108 }
109
Anthony Chen83092c62016-01-11 17:00:36 -0800110 /**
111 * Returns the view that represents the notification area.
112 */
113 public View getNotificationInnerAreaView() {
114 return mNotificationIconArea;
115 }
116
117 /**
Jason Monkaa573e92017-01-27 17:00:29 -0500118 * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
119 * Sets the color that should be used to tint any icons in the notification area.
Jorim Jaggi86905582016-02-09 21:36:09 -0800120 *
121 * @param tintArea the area in which to tint the icons, specified in screen coordinates
Jason Monkaa573e92017-01-27 17:00:29 -0500122 * @param darkIntensity
Jorim Jaggi86905582016-02-09 21:36:09 -0800123 */
Jason Monkaa573e92017-01-27 17:00:29 -0500124 public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
Jorim Jaggi86905582016-02-09 21:36:09 -0800125 if (tintArea == null) {
126 mTintArea.setEmpty();
127 } else {
128 mTintArea.set(tintArea);
129 }
Evan Lairddbeefe32018-04-03 16:52:41 -0400130 if (mNotificationIconArea != null) {
131 if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
132 mIconTint = iconTint;
133 }
134 } else {
135 mIconTint = iconTint;
136 }
137
Anthony Chen83092c62016-01-11 17:00:36 -0800138 applyNotificationIconsTint();
139 }
140
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700141 protected int getHeight() {
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500142 return mStatusBar.getStatusBarHeight();
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700143 }
144
Selim Cinek17e1b692016-12-02 18:19:11 -0800145 protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700146 boolean showAmbient, boolean hideDismissed, boolean hideRepliedMessages) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700147 if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700148 return false;
149 }
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500150 if (!StatusBar.isTopLevelChild(entry)) {
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700151 return false;
152 }
153 if (entry.row.getVisibility() == View.GONE) {
154 return false;
155 }
Selim Cinekd03518c2018-03-15 12:13:51 -0700156 if (entry.row.isDismissed() && hideDismissed) {
157 return false;
158 }
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700159
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700160 if (hideRepliedMessages && entry.isLastMessageFromReply()) {
161 return false;
162 }
163
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500164 // showAmbient == show in shade but not shelf
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400165 if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) {
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500166 return false;
167 }
168
Xiaohui Cheneb04a992016-03-22 14:58:03 -0700169 return true;
170 }
171
Anthony Chen83092c62016-01-11 17:00:36 -0800172 /**
173 * Updates the notifications with the given list of notifications to display.
174 */
Selim Cinekd03518c2018-03-15 12:13:51 -0700175 public void updateNotificationIcons() {
Anthony Chen83092c62016-01-11 17:00:36 -0800176
Selim Cinekd03518c2018-03-15 12:13:51 -0700177 updateStatusBarIcons();
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700178 updateShelfIcons();
179 updateHasShelfIconsWhenFullyDark();
Selim Cinek281c2022016-10-13 19:14:43 -0700180
181 applyNotificationIconsTint();
Selim Cinek281c2022016-10-13 19:14:43 -0700182 }
183
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700184 private void updateHasShelfIconsWhenFullyDark() {
185 boolean hasIconsWhenFullyDark = false;
186 for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
187 View view = mNotificationScrollLayout.getChildAt(i);
188 if (view instanceof ExpandableNotificationRow) {
189 NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
190 if (shouldShowNotificationIcon(ent,
191 NotificationShelf.SHOW_AMBIENT_ICONS /* showAmbient */,
192 false /* hideDismissed */,
193 true /* hideReplied */)) {
194 hasIconsWhenFullyDark = true;
195 break;
196 }
197 }
198 }
199 mHasShelfIconsWhenFullyDark = hasIconsWhenFullyDark;
200 }
201
202 public boolean hasShelfIconsWhenFullyDark() {
203 return mHasShelfIconsWhenFullyDark;
204 }
205
206 private void updateShelfIcons() {
207 updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
208 NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */,
209 mFullyDark /* hideRepliedMessages */);
210 }
211
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700212 public void updateStatusBarIcons() {
Selim Cinekd03518c2018-03-15 12:13:51 -0700213 updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700214 false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
Selim Cinekd03518c2018-03-15 12:13:51 -0700215 }
216
Selim Cinek281c2022016-10-13 19:14:43 -0700217 /**
218 * Updates the notification icons for a host layout. This will ensure that the notification
219 * host layout will have the same icons like the ones in here.
Selim Cinek281c2022016-10-13 19:14:43 -0700220 * @param function A function to look up an icon view based on an entry
221 * @param hostLayout which layout should be updated
Selim Cinek17e1b692016-12-02 18:19:11 -0800222 * @param showAmbient should ambient notification icons be shown
Selim Cinekd03518c2018-03-15 12:13:51 -0700223 * @param hideDismissed should dismissed icons be hidden
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700224 * @param hideRepliedMessages should messages that have been replied to be hidden
Selim Cinek281c2022016-10-13 19:14:43 -0700225 */
Selim Cinekd03518c2018-03-15 12:13:51 -0700226 private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700227 NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
228 boolean hideRepliedMessages) {
Selim Cinek281c2022016-10-13 19:14:43 -0700229 ArrayList<StatusBarIconView> toShow = new ArrayList<>(
230 mNotificationScrollLayout.getChildCount());
Anthony Chen83092c62016-01-11 17:00:36 -0800231
232 // Filter out ambient notifications and notification children.
Selim Cinek281c2022016-10-13 19:14:43 -0700233 for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
234 View view = mNotificationScrollLayout.getChildAt(i);
235 if (view instanceof ExpandableNotificationRow) {
236 NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
Selim Cinekb0dc61b2018-05-22 18:49:36 -0700237 if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,
238 hideRepliedMessages)) {
Selim Cinek281c2022016-10-13 19:14:43 -0700239 toShow.add(function.apply(ent));
240 }
Anthony Chen83092c62016-01-11 17:00:36 -0800241 }
Anthony Chen83092c62016-01-11 17:00:36 -0800242 }
243
Selim Cinek72fc8db2017-06-06 18:07:47 -0700244 // In case we are changing the suppression of a group, the replacement shouldn't flicker
245 // and it should just be replaced instead. We therefore look for notifications that were
246 // just replaced by the child or vice-versa to suppress this.
Selim Cinek49014f82016-11-04 14:55:30 -0700247
Selim Cinek72fc8db2017-06-06 18:07:47 -0700248 ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
Anthony Chen83092c62016-01-11 17:00:36 -0800249 ArrayList<View> toRemove = new ArrayList<>();
Selim Cinek281c2022016-10-13 19:14:43 -0700250 for (int i = 0; i < hostLayout.getChildCount(); i++) {
251 View child = hostLayout.getChildAt(i);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700252 if (!(child instanceof StatusBarIconView)) {
253 continue;
254 }
Anthony Chen83092c62016-01-11 17:00:36 -0800255 if (!toShow.contains(child)) {
Selim Cinek72fc8db2017-06-06 18:07:47 -0700256 boolean iconWasReplaced = false;
257 StatusBarIconView removedIcon = (StatusBarIconView) child;
258 String removedGroupKey = removedIcon.getNotification().getGroupKey();
259 for (int j = 0; j < toShow.size(); j++) {
260 StatusBarIconView candidate = toShow.get(j);
261 if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
262 && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
263 if (!iconWasReplaced) {
264 iconWasReplaced = true;
265 } else {
266 iconWasReplaced = false;
267 break;
268 }
269 }
270 }
271 if (iconWasReplaced) {
272 ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
273 if (statusBarIcons == null) {
274 statusBarIcons = new ArrayList<>();
275 replacingIcons.put(removedGroupKey, statusBarIcons);
276 }
277 statusBarIcons.add(removedIcon.getStatusBarIcon());
278 }
279 toRemove.add(removedIcon);
Anthony Chen83092c62016-01-11 17:00:36 -0800280 }
281 }
Selim Cinek72fc8db2017-06-06 18:07:47 -0700282 // removing all duplicates
283 ArrayList<String> duplicates = new ArrayList<>();
284 for (String key : replacingIcons.keySet()) {
285 ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
286 if (statusBarIcons.size() != 1) {
287 duplicates.add(key);
288 }
289 }
290 replacingIcons.removeAll(duplicates);
291 hostLayout.setReplacingIcons(replacingIcons);
Anthony Chen83092c62016-01-11 17:00:36 -0800292
293 final int toRemoveCount = toRemove.size();
294 for (int i = 0; i < toRemoveCount; i++) {
Selim Cinek281c2022016-10-13 19:14:43 -0700295 hostLayout.removeView(toRemove.get(i));
Anthony Chen83092c62016-01-11 17:00:36 -0800296 }
297
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800298 final FrameLayout.LayoutParams params = generateIconLayoutParams();
Anthony Chen83092c62016-01-11 17:00:36 -0800299 for (int i = 0; i < toShow.size(); i++) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700300 StatusBarIconView v = toShow.get(i);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800301 // The view might still be transiently added if it was just removed and added again
302 hostLayout.removeTransientView(v);
Anthony Chen83092c62016-01-11 17:00:36 -0800303 if (v.getParent() == null) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700304 if (hideDismissed) {
305 v.setOnDismissListener(mUpdateStatusBarIcons);
306 }
Selim Cinek281c2022016-10-13 19:14:43 -0700307 hostLayout.addView(v, i, params);
Anthony Chen83092c62016-01-11 17:00:36 -0800308 }
309 }
310
Selim Cinek5b5beb012016-11-08 18:11:58 -0800311 hostLayout.setChangingViewPositions(true);
Anthony Chen83092c62016-01-11 17:00:36 -0800312 // Re-sort notification icons
Selim Cinek281c2022016-10-13 19:14:43 -0700313 final int childCount = hostLayout.getChildCount();
Anthony Chen83092c62016-01-11 17:00:36 -0800314 for (int i = 0; i < childCount; i++) {
Selim Cinek281c2022016-10-13 19:14:43 -0700315 View actual = hostLayout.getChildAt(i);
Anthony Chen83092c62016-01-11 17:00:36 -0800316 StatusBarIconView expected = toShow.get(i);
317 if (actual == expected) {
318 continue;
319 }
Selim Cinek281c2022016-10-13 19:14:43 -0700320 hostLayout.removeView(expected);
321 hostLayout.addView(expected, i);
Anthony Chen83092c62016-01-11 17:00:36 -0800322 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800323 hostLayout.setChangingViewPositions(false);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700324 hostLayout.setReplacingIcons(null);
Anthony Chen83092c62016-01-11 17:00:36 -0800325 }
326
327 /**
328 * Applies {@link #mIconTint} to the notification icons.
329 */
330 private void applyNotificationIconsTint() {
331 for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
Selim Cinek887da3c2017-10-06 13:37:32 -0700332 final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
333 if (iv.getWidth() != 0) {
334 updateTintForIcon(iv);
335 } else {
336 iv.executeOnLayout(() -> updateTintForIcon(iv));
Anthony Chen83092c62016-01-11 17:00:36 -0800337 }
Anthony Chen83092c62016-01-11 17:00:36 -0800338 }
339 }
Lucas Dupin987f1932017-05-13 21:02:52 -0700340
Selim Cinek887da3c2017-10-06 13:37:32 -0700341 private void updateTintForIcon(StatusBarIconView v) {
342 boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
343 int color = StatusBarIconView.NO_COLOR;
344 boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
345 if (colorize) {
346 color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
347 }
348 v.setStaticDrawableColor(color);
349 v.setDecorColor(mIconTint);
350 }
351
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700352 public void setFullyDark(boolean fullyDark) {
353 mFullyDark = fullyDark;
354 updateShelfIcons();
355 }
356
Lucas Dupin987f1932017-05-13 21:02:52 -0700357 public void setDark(boolean dark) {
358 mNotificationIcons.setDark(dark, false, 0);
359 mShelfIcons.setDark(dark, false, 0);
360 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800361
Selim Cinek332c23f2018-03-16 17:37:50 -0700362 public void showIconIsolated(StatusBarIconView icon, boolean animated) {
363 mNotificationIcons.showIconIsolated(icon, animated);
364 }
365
366 public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
367 mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800368 }
Anthony Chen83092c62016-01-11 17:00:36 -0800369}