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