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