1 /*
2  * Copyright (C) 2018 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 package com.android.car.notification;
17 
18 import android.app.Notification;
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.service.notification.StatusBarNotification;
24 import android.util.Log;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 
29 import androidx.recyclerview.widget.RecyclerView;
30 
31 import com.android.car.notification.template.BasicNotificationViewHolder;
32 import com.android.car.notification.template.CallNotificationViewHolder;
33 import com.android.car.notification.template.CarNotificationFooterViewHolder;
34 import com.android.car.notification.template.CarNotificationHeaderViewHolder;
35 import com.android.car.notification.template.EmergencyNotificationViewHolder;
36 import com.android.car.notification.template.GroupNotificationViewHolder;
37 import com.android.car.notification.template.GroupSummaryNotificationViewHolder;
38 import com.android.car.notification.template.InboxNotificationViewHolder;
39 import com.android.car.notification.template.MessageNotificationViewHolder;
40 import com.android.car.notification.template.ProgressNotificationViewHolder;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * Notification data adapter that binds a notification to the corresponding view.
47  */
48 public class CarNotificationViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
49         implements PreprocessingManager.CallStateListener {
50     private static final String TAG = "CarNotificationAdapter";
51 
52     // Delay in posting notifyDataSetChanged for the adapter in milliseconds.
53     private static final int NOTIFY_DATASET_CHANGED_DELAY = 100;
54 
55     private final Context mContext;
56     private final LayoutInflater mInflater;
57     private final int mMaxNumberGroupChildrenShown;
58     private final boolean mIsGroupNotificationAdapter;
59     private final Handler mHandler = new Handler();
60 
61     // book keeping expanded notification groups
62     private final List<String> mExpandedNotifications = new ArrayList<>();
63 
64     private List<NotificationGroup> mNotifications = new ArrayList<>();
65     private RecyclerView.RecycledViewPool mViewPool;
66     private CarUxRestrictions mCarUxRestrictions;
67     private NotificationClickHandlerFactory mClickHandlerFactory;
68     private NotificationDataManager mNotificationDataManager;
69     private boolean mIsInCall;
70 
71     private Runnable mNotifyDataSetChangedRunnable = this::notifyDataSetChanged;
72 
73     /**
74      * Constructor for a notification adapter.
75      * Can be used both by the root notification list view, or a grouped notification view.
76      *
77      * @param context the context for resources and inflating views
78      * @param isGroupNotificationAdapter true if this adapter is used by a grouped notification view
79      */
CarNotificationViewAdapter(Context context, boolean isGroupNotificationAdapter)80     public CarNotificationViewAdapter(Context context, boolean isGroupNotificationAdapter) {
81         mContext = context;
82         mInflater = LayoutInflater.from(context);
83         mMaxNumberGroupChildrenShown =
84                 mContext.getResources().getInteger(R.integer.max_group_children_number);
85         mIsGroupNotificationAdapter = isGroupNotificationAdapter;
86         setHasStableIds(true);
87         if (!mIsGroupNotificationAdapter) {
88             mViewPool = new RecyclerView.RecycledViewPool();
89         }
90 
91         PreprocessingManager.getInstance(context).addCallStateListener(this::onCallStateChanged);
92     }
93 
94     @Override
onCreateViewHolder(ViewGroup parent, int viewType)95     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
96         RecyclerView.ViewHolder viewHolder;
97         View view;
98         switch (viewType) {
99             case NotificationViewType.HEADER:
100                 view = mInflater.inflate(R.layout.notification_header_template, parent, false);
101                 viewHolder = new CarNotificationHeaderViewHolder(view, mClickHandlerFactory);
102                 break;
103             case NotificationViewType.FOOTER:
104                 view = mInflater.inflate(R.layout.notification_footer_template, parent, false);
105                 viewHolder = new CarNotificationFooterViewHolder(view, mClickHandlerFactory);
106                 break;
107             case NotificationViewType.GROUP_EXPANDED:
108             case NotificationViewType.GROUP_COLLAPSED:
109                 view = mInflater.inflate(
110                         R.layout.group_notification_template, parent, false);
111                 viewHolder = new GroupNotificationViewHolder(view, mClickHandlerFactory);
112                 break;
113             case NotificationViewType.GROUP_SUMMARY:
114                 view = mInflater
115                         .inflate(R.layout.group_summary_notification_template, parent, false);
116                 viewHolder = new GroupSummaryNotificationViewHolder(view, mClickHandlerFactory);
117                 break;
118             case NotificationViewType.CALL:
119                 view = mInflater
120                         .inflate(R.layout.call_notification_template, parent, false);
121                 viewHolder = new CallNotificationViewHolder(view, mClickHandlerFactory);
122                 break;
123             case NotificationViewType.CAR_EMERGENCY:
124                 view = mInflater.inflate(
125                         R.layout.car_emergency_notification_template, parent, false);
126                 viewHolder = new EmergencyNotificationViewHolder(view, mClickHandlerFactory);
127                 break;
128             case NotificationViewType.CAR_WARNING:
129                 view = mInflater.inflate(
130                         R.layout.car_warning_notification_template, parent, false);
131                 // Using the basic view holder because they share the same view binding logic
132                 // OEMs should create view holders if needed
133                 viewHolder = new BasicNotificationViewHolder(view, mClickHandlerFactory);
134                 break;
135             case NotificationViewType.CAR_INFORMATION:
136                 view = mInflater.inflate(
137                         R.layout.car_information_notification_template, parent, false);
138                 // Using the basic view holder because they share the same view binding logic
139                 // OEMs should create view holders if needed
140                 viewHolder = new BasicNotificationViewHolder(view, mClickHandlerFactory);
141                 break;
142             case NotificationViewType.CAR_INFORMATION_IN_GROUP:
143                 view = mInflater.inflate(
144                         R.layout.car_information_notification_template_inner, parent, false);
145                 // Using the basic view holder because they share the same view binding logic
146                 // OEMs should create view holders if needed
147                 viewHolder = new BasicNotificationViewHolder(view, mClickHandlerFactory);
148                 break;
149             case NotificationViewType.MESSAGE_IN_GROUP:
150                 view = mInflater.inflate(
151                         R.layout.message_notification_template_inner, parent, false);
152                 viewHolder = new MessageNotificationViewHolder(view, mClickHandlerFactory);
153                 break;
154             case NotificationViewType.MESSAGE:
155                 view = mInflater.inflate(R.layout.message_notification_template, parent, false);
156                 viewHolder = new MessageNotificationViewHolder(view, mClickHandlerFactory);
157                 break;
158             case NotificationViewType.PROGRESS_IN_GROUP:
159                 view = mInflater.inflate(
160                         R.layout.progress_notification_template_inner, parent, false);
161                 viewHolder = new ProgressNotificationViewHolder(view, mClickHandlerFactory);
162                 break;
163             case NotificationViewType.PROGRESS:
164                 view = mInflater
165                         .inflate(R.layout.progress_notification_template, parent, false);
166                 viewHolder = new ProgressNotificationViewHolder(view, mClickHandlerFactory);
167                 break;
168             case NotificationViewType.INBOX_IN_GROUP:
169                 view = mInflater
170                         .inflate(R.layout.inbox_notification_template_inner, parent, false);
171                 viewHolder = new InboxNotificationViewHolder(view, mClickHandlerFactory);
172                 break;
173             case NotificationViewType.INBOX:
174                 view = mInflater
175                         .inflate(R.layout.inbox_notification_template, parent, false);
176                 viewHolder = new InboxNotificationViewHolder(view, mClickHandlerFactory);
177                 break;
178             case NotificationViewType.BASIC_IN_GROUP:
179                 view = mInflater
180                         .inflate(R.layout.basic_notification_template_inner, parent, false);
181                 viewHolder = new BasicNotificationViewHolder(view, mClickHandlerFactory);
182                 break;
183             case NotificationViewType.BASIC:
184             default:
185                 view = mInflater
186                         .inflate(R.layout.basic_notification_template, parent, false);
187                 viewHolder = new BasicNotificationViewHolder(view, mClickHandlerFactory);
188                 break;
189         }
190         return viewHolder;
191     }
192 
193     @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position)194     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
195         NotificationGroup notificationGroup = mNotifications.get(position);
196 
197         switch (holder.getItemViewType()) {
198             case NotificationViewType.HEADER:
199                 ((CarNotificationHeaderViewHolder) holder).bind(hasNotifications());
200                 break;
201             case NotificationViewType.FOOTER:
202                 ((CarNotificationFooterViewHolder) holder).bind(hasNotifications());
203                 break;
204             case NotificationViewType.GROUP_EXPANDED:
205                 ((GroupNotificationViewHolder) holder)
206                         .bind(notificationGroup, this, /* isExpanded= */ true);
207                 break;
208             case NotificationViewType.GROUP_COLLAPSED:
209                 ((GroupNotificationViewHolder) holder)
210                         .bind(notificationGroup, this, /* isExpanded= */ false);
211                 break;
212             case NotificationViewType.GROUP_SUMMARY:
213                 ((GroupSummaryNotificationViewHolder) holder).bind(notificationGroup);
214                 break;
215             case NotificationViewType.CALL: {
216                 StatusBarNotification notification = notificationGroup.getSingleNotification();
217                 ((CallNotificationViewHolder) holder)
218                         .bind(notification, /* isInGroup= */ false, /* isHeadsUp= */ false);
219                 break;
220             }
221             case NotificationViewType.CAR_EMERGENCY: {
222                 StatusBarNotification notification = notificationGroup.getSingleNotification();
223                 ((EmergencyNotificationViewHolder) holder)
224                         .bind(notification, /* isInGroup= */ false, /* isHeadsUp= */ false);
225                 break;
226             }
227             case NotificationViewType.MESSAGE: {
228                 StatusBarNotification notification = notificationGroup.getSingleNotification();
229                 if (shouldRestrictMessagePreview()) {
230                     ((MessageNotificationViewHolder) holder)
231                             .bindRestricted(notification, /* isInGroup= */ false, /* isHeadsUp= */
232                                     false);
233                 } else {
234                     ((MessageNotificationViewHolder) holder)
235                             .bind(notification, /* isInGroup= */ false, /* isHeadsUp= */ false);
236                 }
237                 break;
238             }
239             case NotificationViewType.MESSAGE_IN_GROUP: {
240                 StatusBarNotification notification = notificationGroup.getSingleNotification();
241                 if (shouldRestrictMessagePreview()) {
242                     ((MessageNotificationViewHolder) holder)
243                             .bindRestricted(notification, /* isInGroup= */ true, /* isHeadsUp= */
244                                     false);
245                 } else {
246                     ((MessageNotificationViewHolder) holder)
247                             .bind(notification, /* isInGroup= */ true, /* isHeadsUp= */ false);
248                 }
249                 break;
250             }
251             case NotificationViewType.PROGRESS: {
252                 StatusBarNotification notification = notificationGroup.getSingleNotification();
253                 ((ProgressNotificationViewHolder) holder)
254                         .bind(notification, /* isInGroup= */ false, false);
255                 break;
256             }
257             case NotificationViewType.PROGRESS_IN_GROUP: {
258                 StatusBarNotification notification = notificationGroup.getSingleNotification();
259                 ((ProgressNotificationViewHolder) holder).bind(notification, /* isInGroup= */
260                         true, false);
261                 break;
262             }
263             case NotificationViewType.INBOX: {
264                 StatusBarNotification notification = notificationGroup.getSingleNotification();
265                 ((InboxNotificationViewHolder) holder).bind(notification, /* isInGroup= */ false,
266                         /* isHeadsUp= */ false);
267                 break;
268             }
269             case NotificationViewType.INBOX_IN_GROUP: {
270                 StatusBarNotification notification = notificationGroup.getSingleNotification();
271                 ((InboxNotificationViewHolder) holder).bind(notification, /* isInGroup= */ true,
272                         /* isHeadsUp= */ false);
273                 break;
274             }
275             case NotificationViewType.CAR_INFORMATION_IN_GROUP:
276             case NotificationViewType.BASIC_IN_GROUP: {
277                 StatusBarNotification notification = notificationGroup.getSingleNotification();
278                 ((BasicNotificationViewHolder) holder).bind(notification, /* isInGroup= */ true,
279                         /* isHeadsUp= */ false);
280                 break;
281             }
282             case NotificationViewType.CAR_WARNING:
283             case NotificationViewType.CAR_INFORMATION:
284             case NotificationViewType.BASIC:
285             default: {
286                 StatusBarNotification notification = notificationGroup.getSingleNotification();
287                 ((BasicNotificationViewHolder) holder).bind(notification, /* isInGroup= */ false,
288                         /* isHeadsUp= */ false);
289                 break;
290             }
291         }
292     }
293 
294     @Override
getItemViewType(int position)295     public int getItemViewType(int position) {
296         NotificationGroup notificationGroup = mNotifications.get(position);
297 
298         if (notificationGroup.isHeader()) {
299             return NotificationViewType.HEADER;
300         }
301 
302         if (notificationGroup.isFooter()) {
303             return NotificationViewType.FOOTER;
304         }
305 
306         if (notificationGroup.isGroup()) {
307             if (mExpandedNotifications.contains(notificationGroup.getGroupKey())) {
308                 return NotificationViewType.GROUP_EXPANDED;
309             } else {
310                 return NotificationViewType.GROUP_COLLAPSED;
311             }
312         } else if (mExpandedNotifications.contains(notificationGroup.getGroupKey())) {
313             // when there are 2 notifications left in the expanded notification and one of them is
314             // removed at that time the item type changes from group to normal and hence the
315             // notification should be removed from expanded notifications.
316             setExpanded(notificationGroup.getGroupKey(), false);
317         }
318 
319         Notification notification =
320                 notificationGroup.getSingleNotification().getNotification();
321         Bundle extras = notification.extras;
322 
323         String category = notification.category;
324         if (category != null) {
325             switch (category) {
326                 case Notification.CATEGORY_CALL:
327                     return NotificationViewType.CALL;
328                 case Notification.CATEGORY_CAR_EMERGENCY:
329                     return NotificationViewType.CAR_EMERGENCY;
330                 case Notification.CATEGORY_CAR_WARNING:
331                     return NotificationViewType.CAR_WARNING;
332                 case Notification.CATEGORY_CAR_INFORMATION:
333                     return mIsGroupNotificationAdapter
334                             ? NotificationViewType.CAR_INFORMATION_IN_GROUP
335                             : NotificationViewType.CAR_INFORMATION;
336                 case Notification.CATEGORY_MESSAGE:
337                     return mIsGroupNotificationAdapter
338                             ? NotificationViewType.MESSAGE_IN_GROUP : NotificationViewType.MESSAGE;
339                 default:
340                     break;
341             }
342         }
343 
344         // progress
345         int progressMax = extras.getInt(Notification.EXTRA_PROGRESS_MAX);
346         boolean isIndeterminate = extras.getBoolean(
347                 Notification.EXTRA_PROGRESS_INDETERMINATE);
348         boolean hasValidProgress = isIndeterminate || progressMax != 0;
349         boolean isProgress = extras.containsKey(Notification.EXTRA_PROGRESS)
350                 && extras.containsKey(Notification.EXTRA_PROGRESS_MAX)
351                 && hasValidProgress
352                 && !notification.hasCompletedProgress();
353         if (isProgress) {
354             return mIsGroupNotificationAdapter
355                     ? NotificationViewType.PROGRESS_IN_GROUP : NotificationViewType.PROGRESS;
356         }
357 
358         // inbox
359         boolean isInbox = extras.containsKey(Notification.EXTRA_TITLE_BIG)
360                 && extras.containsKey(Notification.EXTRA_SUMMARY_TEXT);
361         if (isInbox) {
362             return mIsGroupNotificationAdapter
363                     ? NotificationViewType.INBOX_IN_GROUP : NotificationViewType.INBOX;
364         }
365 
366         // group summary
367         boolean isGroupSummary = notificationGroup.getChildTitles() != null;
368         if (isGroupSummary) {
369             return NotificationViewType.GROUP_SUMMARY;
370         }
371 
372         // the big text and big picture styles are fallen back to basic template in car
373         // i.e. setting the big text and big picture does not have an effect
374         boolean isBigText = extras.containsKey(Notification.EXTRA_BIG_TEXT);
375         if (isBigText) {
376             Log.i(TAG, "Big text style is not supported as a car notification");
377         }
378         boolean isBigPicture = extras.containsKey(Notification.EXTRA_PICTURE);
379         if (isBigPicture) {
380             Log.i(TAG, "Big picture style is not supported as a car notification");
381         }
382 
383         // basic, big text, big picture
384         return mIsGroupNotificationAdapter
385                 ? NotificationViewType.BASIC_IN_GROUP : NotificationViewType.BASIC;
386     }
387 
388     @Override
getItemCount()389     public int getItemCount() {
390         int itemCount = mNotifications.size();
391 
392         if (mIsGroupNotificationAdapter && itemCount > mMaxNumberGroupChildrenShown) {
393             return mMaxNumberGroupChildrenShown;
394         }
395 
396         if (!mIsGroupNotificationAdapter && mCarUxRestrictions != null
397                 && (mCarUxRestrictions.getActiveRestrictions()
398                 & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT) != 0) {
399 
400             int maxItemCount = mCarUxRestrictions.getMaxCumulativeContentItems();
401 
402             return Math.min(itemCount, maxItemCount);
403         }
404         return itemCount;
405     }
406 
407     @Override
getItemId(int position)408     public long getItemId(int position) {
409         NotificationGroup notificationGroup = mNotifications.get(position);
410         if (notificationGroup.isHeader()) {
411             return 0;
412         }
413 
414         if (notificationGroup.isFooter()) {
415             return 1;
416         }
417 
418         return notificationGroup.isGroup()
419                 ? notificationGroup.getGroupKey().hashCode()
420                 : notificationGroup.getSingleNotification().getKey().hashCode();
421     }
422 
423     /**
424      * Set the expansion state of a group notification given its group key.
425      *
426      * @param groupKey the unique identifier of a {@link NotificationGroup}
427      * @param isExpanded whether the group notification should be expanded.
428      */
setExpanded(String groupKey, boolean isExpanded)429     public void setExpanded(String groupKey, boolean isExpanded) {
430         if (isExpanded(groupKey) == isExpanded) {
431             return;
432         }
433 
434         if (isExpanded) {
435             mExpandedNotifications.add(groupKey);
436         } else {
437             mExpandedNotifications.remove(groupKey);
438         }
439     }
440 
441     /**
442      * Collapses all expanded groups.
443      */
collapseAllGroups()444     public void collapseAllGroups() {
445         if (!mExpandedNotifications.isEmpty()) {
446             mExpandedNotifications.clear();
447         }
448     }
449 
450     /**
451      * Returns whether the notification is expanded given its group key.
452      */
isExpanded(String groupKey)453     boolean isExpanded(String groupKey) {
454         return mExpandedNotifications.contains(groupKey);
455     }
456 
457     /**
458      * Gets the current {@link CarUxRestrictions}.
459      */
getCarUxRestrictions()460     public CarUxRestrictions getCarUxRestrictions() {
461         return mCarUxRestrictions;
462     }
463 
464     /**
465      * Clear all notifications.
466      */
clearAllNotifications()467     public void clearAllNotifications() {
468         mClickHandlerFactory.clearAllNotifications();
469     }
470 
471     /**
472      * Updates notifications and update views.
473      *
474      * @param setRecyclerViewListHeaderAndFooter sets the header and footer on the entire list of
475      * items within the recycler view. This is NOT the header/footer for the grouped notifications.
476      */
setNotifications(List<NotificationGroup> notifications, boolean setRecyclerViewListHeaderAndFooter)477     public void setNotifications(List<NotificationGroup> notifications,
478             boolean setRecyclerViewListHeaderAndFooter) {
479 
480         List<NotificationGroup> notificationGroupList = new ArrayList<>(notifications);
481 
482         if (setRecyclerViewListHeaderAndFooter) {
483             // add header as the first item of the list.
484             notificationGroupList.add(0, createNotificationHeader());
485             // add footer as the last item of the list.
486             notificationGroupList.add(createNotificationFooter());
487         }
488 
489         mNotifications = notificationGroupList;
490 
491         mHandler.removeCallbacks(mNotifyDataSetChangedRunnable);
492         mHandler.postDelayed(mNotifyDataSetChangedRunnable, NOTIFY_DATASET_CHANGED_DELAY);
493     }
494 
495     /**
496      * Notification list has header and footer by default. Therefore the min number of items in the
497      * adapter will always be two. If there are any notifications present the size will be more than
498      * two.
499      */
hasNotifications()500     private boolean hasNotifications() {
501         return getItemCount() > 2;
502     }
503 
createNotificationHeader()504     private NotificationGroup createNotificationHeader() {
505         NotificationGroup notificationGroupWithHeader = new NotificationGroup();
506         notificationGroupWithHeader.setHeader(true);
507         notificationGroupWithHeader.setGroupKey("notification_header");
508         return notificationGroupWithHeader;
509     }
510 
createNotificationFooter()511     private NotificationGroup createNotificationFooter() {
512         NotificationGroup notificationGroupWithFooter = new NotificationGroup();
513         notificationGroupWithFooter.setFooter(true);
514         notificationGroupWithFooter.setGroupKey("notification_footer");
515         return notificationGroupWithFooter;
516     }
517 
518     /** Implementation of {@link PreprocessingManager.CallStateListener} **/
519     @Override
onCallStateChanged(boolean isInCall)520     public void onCallStateChanged(boolean isInCall) {
521         if (isInCall != mIsInCall) {
522             mIsInCall = isInCall;
523             notifyDataSetChanged();
524         }
525     }
526 
527     /**
528      * Sets the current {@link CarUxRestrictions}.
529      */
setCarUxRestrictions(CarUxRestrictions carUxRestrictions)530     public void setCarUxRestrictions(CarUxRestrictions carUxRestrictions) {
531         Log.d(TAG, "setCarUxRestrictions");
532         mCarUxRestrictions = carUxRestrictions;
533         notifyDataSetChanged();
534     }
535 
536     /**
537      * Helper method that determines whether a notification is a messaging notification and
538      * should have restricted content (no message preview).
539      */
shouldRestrictMessagePreview()540     private boolean shouldRestrictMessagePreview() {
541         return mCarUxRestrictions != null && (mCarUxRestrictions.getActiveRestrictions()
542                 & CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE) != 0;
543     }
544 
545     /**
546      * Get root recycler view's view pool so that the child recycler view can share the same
547      * view pool with the parent.
548      */
getViewPool()549     public RecyclerView.RecycledViewPool getViewPool() {
550         if (mIsGroupNotificationAdapter) {
551             // currently only support one level of expansion.
552             throw new IllegalStateException("CarNotificationViewAdapter is a child adapter; "
553                     + "its view pool should not be reused.");
554         }
555         return mViewPool;
556     }
557 
558     /**
559      * Sets the NotificationClickHandlerFactory that allows for a hook to run a block off code
560      * when  the notification is clicked. This is useful to dismiss a screen after
561      * a notification list clicked.
562      */
setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory)563     public void setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory) {
564         mClickHandlerFactory = clickHandlerFactory;
565     }
566 
567     /**
568      * Sets NotificationDataManager that handles additional states for notifications such as "seen",
569      * and muting a messaging type notification.
570      *
571      * @param notificationDataManager An instance of NotificationDataManager.
572      */
setNotificationDataManager(NotificationDataManager notificationDataManager)573     public void setNotificationDataManager(NotificationDataManager notificationDataManager) {
574         mNotificationDataManager = notificationDataManager;
575     }
576 
577     /**
578      * Set the notification group as seen.
579      *
580      * @param position Adapter position of the notification group.
581      */
setNotificationAsSeen(int position)582     public void setNotificationAsSeen(int position) {
583         NotificationGroup notificationGroup = null;
584 
585         try {
586             notificationGroup = mNotifications.get(position);
587         } catch (IndexOutOfBoundsException e) {
588             Log.e(TAG, "trying to mark none existent notification as seen.");
589             return;
590         }
591 
592         if (mNotificationDataManager != null) {
593             for (StatusBarNotification notification : notificationGroup.getChildNotifications()) {
594                 mNotificationDataManager.setNotificationAsSeen(notification);
595             }
596         }
597     }
598 }
599