blob: 7f95d48f36d46e308016283a9f8e0af0dfdbef88 [file] [log] [blame]
Selim Cinekea4bef72015-12-02 15:51:10 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar;
18
19import android.app.Notification;
20import android.graphics.PorterDuff;
21import android.graphics.drawable.Icon;
22import android.text.TextUtils;
23import android.view.NotificationHeaderView;
24import android.view.View;
25import android.widget.ImageView;
26import android.widget.TextView;
27
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.List;
31
32/**
33 * A Util to manage {@link android.view.NotificationHeaderView} objects and their redundancies.
34 */
35public class NotificationHeaderUtil {
36
37 private static final TextViewComparator sTextViewComparator = new TextViewComparator();
38 private static final VisibilityApplicator sVisibilityApplicator = new VisibilityApplicator();
39 private static final DataExtractor sIconExtractor = new DataExtractor() {
40 @Override
41 public Object extractData(ExpandableNotificationRow row) {
42 return row.getStatusBarNotification().getNotification();
43 }
44 };
45 private static final IconComparator sIconVisibilityComparator = new IconComparator() {
46 public boolean compare(View parent, View child, Object parentData,
47 Object childData) {
48 return hasSameIcon(parentData, childData)
49 && hasSameColor(parentData, childData);
50 }
51 };
52 private static final IconComparator sGreyComparator = new IconComparator() {
53 public boolean compare(View parent, View child, Object parentData,
54 Object childData) {
55 return !hasSameIcon(parentData, childData)
56 || hasSameColor(parentData, childData);
57 }
58 };
59 private final static ResultApplicator mGreyApplicator = new ResultApplicator() {
60 @Override
61 public void apply(View view, boolean apply) {
62 NotificationHeaderView header = (NotificationHeaderView) view;
63 ImageView icon = (ImageView) view.findViewById(
64 com.android.internal.R.id.icon);
65 ImageView expand = (ImageView) view.findViewById(
66 com.android.internal.R.id.expand_button);
67 applyToChild(icon, apply, header.getOriginalIconColor());
Selim Cinekb85f36fd2016-04-20 18:46:36 -070068 applyToChild(expand, apply, header.getOriginalNotificationColor());
Selim Cinekea4bef72015-12-02 15:51:10 -080069 }
70
71 private void applyToChild(View view, boolean shouldApply, int originalColor) {
Selim Cineke3efd832016-02-02 16:54:02 -080072 if (originalColor != NotificationHeaderView.NO_COLOR) {
Selim Cinekea4bef72015-12-02 15:51:10 -080073 ImageView imageView = (ImageView) view;
74 imageView.getDrawable().mutate();
75 if (shouldApply) {
76 // lets gray it out
77 int grey = view.getContext().getColor(
78 com.android.internal.R.color.notification_icon_default_color);
79 imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
80 } else {
81 // lets reset it
82 imageView.getDrawable().setColorFilter(originalColor,
83 PorterDuff.Mode.SRC_ATOP);
84 }
85 }
86 }
87 };
88
89 private final ExpandableNotificationRow mRow;
90 private final ArrayList<HeaderProcessor> mComparators = new ArrayList<>();
91 private final HashSet<Integer> mDividers = new HashSet<>();
92
Mady Mellorb0a82462016-04-30 17:31:02 -070093 public NotificationHeaderUtil(ExpandableNotificationRow row) {
Selim Cinekea4bef72015-12-02 15:51:10 -080094 mRow = row;
95 // To hide the icons if they are the same and the color is the same
96 mComparators.add(new HeaderProcessor(mRow,
97 com.android.internal.R.id.icon,
98 sIconExtractor,
99 sIconVisibilityComparator,
100 sVisibilityApplicator));
101 // To grey them out the icons and expand button when the icons are not the same
102 mComparators.add(new HeaderProcessor(mRow,
103 com.android.internal.R.id.notification_header,
104 sIconExtractor,
105 sGreyComparator,
106 mGreyApplicator));
Selim Cinekc848c3a2016-01-13 15:27:30 -0800107 mComparators.add(new HeaderProcessor(mRow,
108 com.android.internal.R.id.profile_badge,
109 null /* Extractor */,
110 new ViewComparator() {
111 @Override
112 public boolean compare(View parent, View child, Object parentData,
113 Object childData) {
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700114 return parent.getVisibility() != View.GONE;
Selim Cinekc848c3a2016-01-13 15:27:30 -0800115 }
116
117 @Override
118 public boolean isEmpty(View view) {
119 if (view instanceof ImageView) {
120 return ((ImageView) view).getDrawable() == null;
121 }
122 return false;
123 }
124 },
125 sVisibilityApplicator));
Selim Cinekea4bef72015-12-02 15:51:10 -0800126 mComparators.add(HeaderProcessor.forTextView(mRow,
127 com.android.internal.R.id.app_name_text));
128 mComparators.add(HeaderProcessor.forTextView(mRow,
Selim Cinek0f9dd1e2016-04-05 17:03:40 -0700129 com.android.internal.R.id.header_text));
130 mDividers.add(com.android.internal.R.id.header_text_divider);
Selim Cinekea4bef72015-12-02 15:51:10 -0800131 mDividers.add(com.android.internal.R.id.time_divider);
132 }
133
134 public void updateChildrenHeaderAppearance() {
135 List<ExpandableNotificationRow> notificationChildren = mRow.getNotificationChildren();
136 if (notificationChildren == null) {
137 return;
138 }
139 // Initialize the comparators
140 for (int compI = 0; compI < mComparators.size(); compI++) {
141 mComparators.get(compI).init();
142 }
143
144 // Compare all notification headers
145 for (int i = 0; i < notificationChildren.size(); i++) {
146 ExpandableNotificationRow row = notificationChildren.get(i);
147 for (int compI = 0; compI < mComparators.size(); compI++) {
148 mComparators.get(compI).compareToHeader(row);
149 }
150 }
151
152 // Apply the comparison to the row
153 for (int i = 0; i < notificationChildren.size(); i++) {
154 ExpandableNotificationRow row = notificationChildren.get(i);
155 for (int compI = 0; compI < mComparators.size(); compI++) {
156 mComparators.get(compI).apply(row);
157 }
158 // We need to sanitize the dividers since they might be off-balance now
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700159 sanitizeHeaderViews(row);
Selim Cinekea4bef72015-12-02 15:51:10 -0800160 }
161 }
162
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700163 private void sanitizeHeaderViews(ExpandableNotificationRow row) {
Selim Cinekea4bef72015-12-02 15:51:10 -0800164 if (row.isSummaryWithChildren()) {
165 sanitizeHeader(row.getNotificationHeader());
166 return;
167 }
168 final NotificationContentView layout = row.getPrivateLayout();
169 sanitizeChild(layout.getContractedChild());
170 sanitizeChild(layout.getHeadsUpChild());
171 sanitizeChild(layout.getExpandedChild());
172 }
173
174 private void sanitizeChild(View child) {
175 if (child != null) {
176 NotificationHeaderView header = (NotificationHeaderView) child.findViewById(
177 com.android.internal.R.id.notification_header);
178 sanitizeHeader(header);
179 }
180 }
181
182 private void sanitizeHeader(NotificationHeaderView rowHeader) {
183 if (rowHeader == null) {
184 return;
185 }
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700186 final int childCount = rowHeader.getChildCount();
187 View time = rowHeader.findViewById(com.android.internal.R.id.time);
188 boolean hasVisibleText = false;
189 for (int i = 1; i < childCount - 1 ; i++) {
190 View child = rowHeader.getChildAt(i);
191 if (child instanceof TextView
192 && child.getVisibility() != View.GONE
193 && !mDividers.contains(Integer.valueOf(child.getId()))
194 && child != time) {
195 hasVisibleText = true;
196 break;
197 }
198 }
199 // in case no view is visible we make sure the time is visible
200 int timeVisibility = !hasVisibleText
Selim Cinekc2c0b042016-05-18 17:13:46 -0700201 || mRow.getStatusBarNotification().getNotification().showsTime()
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700202 ? View.VISIBLE : View.GONE;
203 time.setVisibility(timeVisibility);
Selim Cinekea4bef72015-12-02 15:51:10 -0800204 View left = null;
205 View right;
Selim Cinekea4bef72015-12-02 15:51:10 -0800206 for (int i = 1; i < childCount - 1 ; i++) {
207 View child = rowHeader.getChildAt(i);
208 if (mDividers.contains(Integer.valueOf(child.getId()))) {
209 boolean visible = false;
210 // Lets find the item to the right
211 for (i++; i < childCount - 1; i++) {
212 right = rowHeader.getChildAt(i);
213 if (mDividers.contains(Integer.valueOf(right.getId()))) {
214 // A divider was found, this needs to be hidden
215 i--;
216 break;
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700217 } else if (right.getVisibility() != View.GONE && right instanceof TextView) {
Selim Cinekea4bef72015-12-02 15:51:10 -0800218 visible = left != null;
219 left = right;
220 break;
221 }
222 }
223 child.setVisibility(visible ? View.VISIBLE : View.GONE);
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700224 } else if (child.getVisibility() != View.GONE && child instanceof TextView) {
Selim Cinekea4bef72015-12-02 15:51:10 -0800225 left = child;
226 }
227 }
228 }
229
230 public void restoreNotificationHeader(ExpandableNotificationRow row) {
231 for (int compI = 0; compI < mComparators.size(); compI++) {
232 mComparators.get(compI).apply(row, true /* reset */);
233 }
Selim Cinekb85f36fd2016-04-20 18:46:36 -0700234 sanitizeHeaderViews(row);
Selim Cinekea4bef72015-12-02 15:51:10 -0800235 }
236
237 private static class HeaderProcessor {
238 private final int mId;
239 private final DataExtractor mExtractor;
240 private final ResultApplicator mApplicator;
241 private final ExpandableNotificationRow mParentRow;
242 private boolean mApply;
243 private View mParentView;
244 private ViewComparator mComparator;
245 private Object mParentData;
246
247 public static HeaderProcessor forTextView(ExpandableNotificationRow row, int id) {
248 return new HeaderProcessor(row, id, null, sTextViewComparator, sVisibilityApplicator);
249 }
250
251 HeaderProcessor(ExpandableNotificationRow row, int id, DataExtractor extractor,
252 ViewComparator comparator,
253 ResultApplicator applicator) {
254 mId = id;
255 mExtractor = extractor;
256 mApplicator = applicator;
257 mComparator = comparator;
258 mParentRow = row;
259 }
260
261 public void init() {
262 mParentView = mParentRow.getNotificationHeader().findViewById(mId);
263 mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
264 mApply = !mComparator.isEmpty(mParentView);
265 }
266 public void compareToHeader(ExpandableNotificationRow row) {
267 if (!mApply) {
268 return;
269 }
270 NotificationHeaderView header = row.getNotificationHeader();
271 if (header == null) {
272 mApply = false;
273 return;
274 }
275 Object childData = mExtractor == null ? null : mExtractor.extractData(row);
276 mApply = mComparator.compare(mParentView, header.findViewById(mId),
277 mParentData, childData);
278 }
279
280 public void apply(ExpandableNotificationRow row) {
281 apply(row, false /* reset */);
282 }
283
284 public void apply(ExpandableNotificationRow row, boolean reset) {
285 boolean apply = mApply && !reset;
286 if (row.isSummaryWithChildren()) {
287 applyToView(apply, row.getNotificationHeader());
288 return;
289 }
290 applyToView(apply, row.getPrivateLayout().getContractedChild());
291 applyToView(apply, row.getPrivateLayout().getHeadsUpChild());
292 applyToView(apply, row.getPrivateLayout().getExpandedChild());
293 }
294
295 private void applyToView(boolean apply, View parent) {
296 if (parent != null) {
297 View view = parent.findViewById(mId);
298 if (view != null && !mComparator.isEmpty(view)) {
299 mApplicator.apply(view, apply);
300 }
301 }
302 }
303 }
304
305 private interface ViewComparator {
306 /**
307 * @param parent the parent view
308 * @param child the child view
309 * @param parentData optional data for the parent
310 * @param childData optional data for the child
311 * @return whether to views are the same
312 */
313 boolean compare(View parent, View child, Object parentData, Object childData);
314 boolean isEmpty(View view);
315 }
316
317 private interface DataExtractor {
318 Object extractData(ExpandableNotificationRow row);
319 }
320
321 private static class TextViewComparator implements ViewComparator {
322 @Override
323 public boolean compare(View parent, View child, Object parentData, Object childData) {
324 TextView parentView = (TextView) parent;
325 TextView childView = (TextView) child;
326 return parentView.getText().equals(childView.getText());
327 }
328
329 @Override
330 public boolean isEmpty(View view) {
331 return TextUtils.isEmpty(((TextView) view).getText());
332 }
333 }
334
335 private static abstract class IconComparator implements ViewComparator {
336 @Override
337 public boolean compare(View parent, View child, Object parentData, Object childData) {
338 return false;
339 }
340
341 protected boolean hasSameIcon(Object parentData, Object childData) {
342 Icon parentIcon = ((Notification) parentData).getSmallIcon();
343 Icon childIcon = ((Notification) childData).getSmallIcon();
344 return parentIcon.sameAs(childIcon);
345 }
346
347 /**
348 * @return whether two ImageViews have the same colorFilterSet or none at all
349 */
350 protected boolean hasSameColor(Object parentData, Object childData) {
351 int parentColor = ((Notification) parentData).color;
352 int childColor = ((Notification) childData).color;
353 return parentColor == childColor;
354 }
355
356 @Override
357 public boolean isEmpty(View view) {
358 return false;
359 }
360 }
361
362 private interface ResultApplicator {
363 void apply(View view, boolean apply);
364 }
365
366 private static class VisibilityApplicator implements ResultApplicator {
367
368 @Override
369 public void apply(View view, boolean apply) {
370 view.setVisibility(apply ? View.GONE : View.VISIBLE);
371 }
372 }
373}