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