• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.statusbar;
18 
19 import android.app.Notification;
20 import android.graphics.PorterDuff;
21 import android.graphics.drawable.Icon;
22 import android.text.TextUtils;
23 import android.view.NotificationHeaderView;
24 import android.view.View;
25 import android.widget.ImageView;
26 import android.widget.TextView;
27 
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
31 
32 /**
33  * A Util to manage {@link android.view.NotificationHeaderView} objects and their redundancies.
34  */
35 public 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());
68             applyToChild(expand, apply, header.getOriginalNotificationColor());
69         }
70 
71         private void applyToChild(View view, boolean shouldApply, int originalColor) {
72             if (originalColor != NotificationHeaderView.NO_COLOR) {
73                 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 
NotificationHeaderUtil(ExpandableNotificationRow row)93     public NotificationHeaderUtil(ExpandableNotificationRow row) {
94         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));
107         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) {
114                         return parent.getVisibility() != View.GONE;
115                     }
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));
126         mComparators.add(HeaderProcessor.forTextView(mRow,
127                 com.android.internal.R.id.app_name_text));
128         mComparators.add(HeaderProcessor.forTextView(mRow,
129                 com.android.internal.R.id.header_text));
130         mDividers.add(com.android.internal.R.id.header_text_divider);
131         mDividers.add(com.android.internal.R.id.time_divider);
132     }
133 
updateChildrenHeaderAppearance()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
159             sanitizeHeaderViews(row);
160         }
161     }
162 
sanitizeHeaderViews(ExpandableNotificationRow row)163     private void sanitizeHeaderViews(ExpandableNotificationRow row) {
164         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 
sanitizeChild(View child)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 
sanitizeHeader(NotificationHeaderView rowHeader)182     private void sanitizeHeader(NotificationHeaderView rowHeader) {
183         if (rowHeader == null) {
184             return;
185         }
186         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
201                 || mRow.getStatusBarNotification().getNotification().showsTime()
202                 ? View.VISIBLE : View.GONE;
203         time.setVisibility(timeVisibility);
204         View left = null;
205         View right;
206         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;
217                     } else if (right.getVisibility() != View.GONE && right instanceof TextView) {
218                         visible = left != null;
219                         left = right;
220                         break;
221                     }
222                 }
223                 child.setVisibility(visible ? View.VISIBLE : View.GONE);
224             } else if (child.getVisibility() != View.GONE && child instanceof TextView) {
225                 left = child;
226             }
227         }
228     }
229 
restoreNotificationHeader(ExpandableNotificationRow row)230     public void restoreNotificationHeader(ExpandableNotificationRow row) {
231         for (int compI = 0; compI < mComparators.size(); compI++) {
232             mComparators.get(compI).apply(row, true /* reset */);
233         }
234         sanitizeHeaderViews(row);
235     }
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 
forTextView(ExpandableNotificationRow row, int id)247         public static HeaderProcessor forTextView(ExpandableNotificationRow row, int id) {
248             return new HeaderProcessor(row, id, null, sTextViewComparator, sVisibilityApplicator);
249         }
250 
HeaderProcessor(ExpandableNotificationRow row, int id, DataExtractor extractor, ViewComparator comparator, ResultApplicator applicator)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 
init()261         public void init() {
262             mParentView = mParentRow.getNotificationHeader().findViewById(mId);
263             mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
264             mApply = !mComparator.isEmpty(mParentView);
265         }
compareToHeader(ExpandableNotificationRow row)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 
apply(ExpandableNotificationRow row)280         public void apply(ExpandableNotificationRow row) {
281             apply(row, false /* reset */);
282         }
283 
apply(ExpandableNotificationRow row, boolean reset)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 
applyToView(boolean apply, View parent)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          */
compare(View parent, View child, Object parentData, Object childData)313         boolean compare(View parent, View child, Object parentData, Object childData);
isEmpty(View view)314         boolean isEmpty(View view);
315     }
316 
317     private interface DataExtractor {
extractData(ExpandableNotificationRow row)318         Object extractData(ExpandableNotificationRow row);
319     }
320 
321     private static class TextViewComparator implements ViewComparator {
322         @Override
compare(View parent, View child, Object parentData, Object childData)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
isEmpty(View view)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
compare(View parent, View child, Object parentData, Object childData)337         public boolean compare(View parent, View child, Object parentData, Object childData) {
338             return false;
339         }
340 
hasSameIcon(Object parentData, Object childData)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          */
hasSameColor(Object parentData, Object childData)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
isEmpty(View view)357         public boolean isEmpty(View view) {
358             return false;
359         }
360     }
361 
362     private interface ResultApplicator {
apply(View view, boolean apply)363         void apply(View view, boolean apply);
364     }
365 
366     private static class VisibilityApplicator implements ResultApplicator {
367 
368         @Override
apply(View view, boolean apply)369         public void apply(View view, boolean apply) {
370             view.setVisibility(apply ? View.GONE : View.VISIBLE);
371         }
372     }
373 }
374