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