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.annotation.NonNull; 19 import android.app.Notification; 20 import android.car.drivingstate.CarUxRestrictions; 21 import android.content.Context; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.util.Log; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 29 import androidx.annotation.Nullable; 30 import androidx.recyclerview.widget.DiffUtil; 31 import androidx.recyclerview.widget.LinearLayoutManager; 32 import androidx.recyclerview.widget.RecyclerView; 33 34 import com.android.car.notification.template.CarNotificationBaseViewHolder; 35 import com.android.car.notification.template.CarNotificationFooterViewHolder; 36 import com.android.car.notification.template.CarNotificationHeaderViewHolder; 37 import com.android.car.notification.template.CarNotificationOlderViewHolder; 38 import com.android.car.notification.template.CarNotificationRecentsViewHolder; 39 import com.android.car.notification.template.GroupNotificationViewHolder; 40 import com.android.car.notification.template.GroupSummaryNotificationViewHolder; 41 import com.android.car.notification.template.MessageNotificationViewHolder; 42 import com.android.car.ui.recyclerview.ContentLimitingAdapter; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * Notification data adapter that binds a notification to the corresponding view. 49 */ 50 public class CarNotificationViewAdapter extends ContentLimitingAdapter<RecyclerView.ViewHolder> 51 implements PreprocessingManager.CallStateListener { 52 private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG; 53 private static final String TAG = "CarNotificationAdapter"; 54 private static final int ID_HEADER = 0; 55 private static final int ID_RECENT_HEADER = 1; 56 private static final int ID_OLDER_HEADER = 2; 57 private static final int ID_FOOTER = 3; 58 59 private final Context mContext; 60 private final LayoutInflater mInflater; 61 private final int mMaxNumberGroupChildrenShown; 62 private final boolean mIsGroupNotificationAdapter; 63 private final boolean mShowRecentsAndOlderHeaders; 64 65 // book keeping expanded notification groups 66 private final List<ExpandedNotification> mExpandedNotifications = new ArrayList<>(); 67 private final CarNotificationItemController mNotificationItemController; 68 69 private List<NotificationGroup> mNotifications = new ArrayList<>(); 70 private LinearLayoutManager mLayoutManager; 71 private RecyclerView.RecycledViewPool mViewPool; 72 private CarUxRestrictions mCarUxRestrictions; 73 private NotificationClickHandlerFactory mClickHandlerFactory; 74 private NotificationDataManager mNotificationDataManager; 75 private boolean mIsInCall; 76 private boolean mHasHeaderAndFooter; 77 private boolean mHasUnseenNotifications; 78 private boolean mHasSeenNotifications; 79 private int mMaxItems = ContentLimitingAdapter.UNLIMITED; 80 81 /** 82 * Constructor for a notification adapter. 83 * Can be used both by the root notification list view, or a grouped notification view. 84 * 85 * @param context the context for resources and inflating views 86 * @param isGroupNotificationAdapter true if this adapter is used by a grouped notification view 87 * @param notificationItemController shared logic to control notification items. 88 */ CarNotificationViewAdapter(Context context, boolean isGroupNotificationAdapter, @Nullable CarNotificationItemController notificationItemController)89 public CarNotificationViewAdapter(Context context, boolean isGroupNotificationAdapter, 90 @Nullable CarNotificationItemController notificationItemController) { 91 mContext = context; 92 mInflater = LayoutInflater.from(context); 93 mMaxNumberGroupChildrenShown = 94 mContext.getResources().getInteger(R.integer.max_group_children_number); 95 mShowRecentsAndOlderHeaders = 96 mContext.getResources().getBoolean(R.bool.config_showRecentAndOldHeaders); 97 mIsGroupNotificationAdapter = isGroupNotificationAdapter; 98 mNotificationItemController = notificationItemController; 99 mNotificationDataManager = NotificationDataManager.getInstance(); 100 setHasStableIds(true); 101 if (!mIsGroupNotificationAdapter) { 102 mViewPool = new RecyclerView.RecycledViewPool(); 103 } 104 105 PreprocessingManager.getInstance(context).addCallStateListener(this::onCallStateChanged); 106 } 107 108 @Override onAttachedToRecyclerView(@onNull RecyclerView recyclerView)109 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 110 super.onAttachedToRecyclerView(recyclerView); 111 mLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 112 } 113 114 @Override onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)115 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 116 super.onDetachedFromRecyclerView(recyclerView); 117 mLayoutManager = null; 118 } 119 120 @Override onCreateViewHolderImpl(@onNull ViewGroup parent, int viewType)121 public RecyclerView.ViewHolder onCreateViewHolderImpl(@NonNull ViewGroup parent, int viewType) { 122 RecyclerView.ViewHolder viewHolder; 123 View view; 124 switch (viewType) { 125 case NotificationViewType.HEADER: 126 view = mInflater.inflate(R.layout.notification_header_template, parent, false); 127 viewHolder = new CarNotificationHeaderViewHolder(mContext, view, 128 mNotificationItemController, mClickHandlerFactory); 129 break; 130 case NotificationViewType.FOOTER: 131 view = mInflater.inflate(R.layout.notification_footer_template, parent, false); 132 viewHolder = new CarNotificationFooterViewHolder(mContext, view, 133 mNotificationItemController, mClickHandlerFactory); 134 break; 135 case NotificationViewType.RECENTS: 136 view = mInflater.inflate(R.layout.notification_recents_template, parent, false); 137 viewHolder = new CarNotificationRecentsViewHolder(mContext, view, 138 mNotificationItemController); 139 break; 140 case NotificationViewType.OLDER: 141 view = mInflater.inflate(R.layout.notification_older_template, parent, false); 142 viewHolder = new CarNotificationOlderViewHolder(mContext, view, 143 mNotificationItemController); 144 break; 145 default: 146 CarNotificationTypeItem carNotificationTypeItem = CarNotificationTypeItem.of( 147 viewType); 148 view = mInflater.inflate( 149 carNotificationTypeItem.getNotificationCenterTemplate(), parent, false); 150 viewHolder = carNotificationTypeItem.getViewHolder(view, mClickHandlerFactory); 151 } 152 153 return viewHolder; 154 } 155 156 @Override onBindViewHolderImpl(RecyclerView.ViewHolder holder, int position)157 public void onBindViewHolderImpl(RecyclerView.ViewHolder holder, int position) { 158 NotificationGroup notificationGroup = mNotifications.get(position); 159 160 int viewType = holder.getItemViewType(); 161 switch (viewType) { 162 case NotificationViewType.HEADER: 163 ((CarNotificationHeaderViewHolder) holder).bind(hasNotifications()); 164 return; 165 case NotificationViewType.FOOTER: 166 ((CarNotificationFooterViewHolder) holder).bind(hasNotifications()); 167 return; 168 case NotificationViewType.RECENTS: 169 ((CarNotificationRecentsViewHolder) holder).bind(mHasUnseenNotifications); 170 return; 171 case NotificationViewType.OLDER: 172 ((CarNotificationOlderViewHolder) holder) 173 .bind(mHasSeenNotifications, !mHasUnseenNotifications); 174 return; 175 case NotificationViewType.GROUP_EXPANDED: 176 ((GroupNotificationViewHolder) holder) 177 .bind(notificationGroup, this, /* isExpanded= */ true); 178 return; 179 case NotificationViewType.GROUP_COLLAPSED: 180 ((GroupNotificationViewHolder) holder) 181 .bind(notificationGroup, this, /* isExpanded= */ false); 182 return; 183 case NotificationViewType.GROUP_SUMMARY: 184 ((CarNotificationBaseViewHolder) holder).setHideDismissButton(true); 185 ((GroupSummaryNotificationViewHolder) holder).bind(notificationGroup); 186 return; 187 } 188 189 CarNotificationTypeItem carNotificationTypeItem = CarNotificationTypeItem.of(viewType); 190 AlertEntry alertEntry = notificationGroup.getSingleNotification(); 191 192 if (shouldRestrictMessagePreview() && (viewType == NotificationViewType.MESSAGE 193 || viewType == NotificationViewType.MESSAGE_IN_GROUP)) { 194 ((MessageNotificationViewHolder) holder) 195 .bindRestricted(alertEntry, /* isInGroup= */ false, /* isHeadsUp= */false); 196 } else { 197 carNotificationTypeItem.bind(alertEntry, false, (CarNotificationBaseViewHolder) holder); 198 } 199 } 200 201 @Override getItemViewTypeImpl(int position)202 public int getItemViewTypeImpl(int position) { 203 NotificationGroup notificationGroup = mNotifications.get(position); 204 if (notificationGroup.isHeader()) { 205 return NotificationViewType.HEADER; 206 } 207 208 if (notificationGroup.isFooter()) { 209 return NotificationViewType.FOOTER; 210 } 211 212 if (notificationGroup.isRecentsHeader()) { 213 return NotificationViewType.RECENTS; 214 } 215 216 if (notificationGroup.isOlderHeader()) { 217 return NotificationViewType.OLDER; 218 } 219 220 ExpandedNotification expandedNotification = 221 new ExpandedNotification(notificationGroup.getGroupKey(), 222 notificationGroup.isSeen()); 223 if (notificationGroup.isGroup()) { 224 if (mExpandedNotifications.contains(expandedNotification)) { 225 return NotificationViewType.GROUP_EXPANDED; 226 } else { 227 return NotificationViewType.GROUP_COLLAPSED; 228 } 229 } else if (mExpandedNotifications.contains(expandedNotification)) { 230 // when there are 2 notifications left in the expanded notification and one of them is 231 // removed at that time the item type changes from group to normal and hence the 232 // notification should be removed from expanded notifications. 233 setExpanded(expandedNotification.getKey(), expandedNotification.isExpanded(), 234 /* isExpanded= */ false); 235 } 236 237 Notification notification = 238 notificationGroup.getSingleNotification().getNotification(); 239 Bundle extras = notification.extras; 240 241 String category = notification.category; 242 if (category != null) { 243 switch (category) { 244 case Notification.CATEGORY_CALL: 245 return NotificationViewType.CALL; 246 case Notification.CATEGORY_CAR_EMERGENCY: 247 return NotificationViewType.CAR_EMERGENCY; 248 case Notification.CATEGORY_CAR_WARNING: 249 return NotificationViewType.CAR_WARNING; 250 case Notification.CATEGORY_CAR_INFORMATION: 251 return mIsGroupNotificationAdapter 252 ? NotificationViewType.CAR_INFORMATION_IN_GROUP 253 : NotificationViewType.CAR_INFORMATION; 254 case Notification.CATEGORY_MESSAGE: 255 return mIsGroupNotificationAdapter 256 ? NotificationViewType.MESSAGE_IN_GROUP : NotificationViewType.MESSAGE; 257 default: 258 break; 259 } 260 } 261 262 // progress 263 int progressMax = extras.getInt(Notification.EXTRA_PROGRESS_MAX); 264 boolean isIndeterminate = extras.getBoolean( 265 Notification.EXTRA_PROGRESS_INDETERMINATE); 266 boolean hasValidProgress = isIndeterminate || progressMax != 0; 267 boolean isProgress = extras.containsKey(Notification.EXTRA_PROGRESS) 268 && extras.containsKey(Notification.EXTRA_PROGRESS_MAX) 269 && hasValidProgress 270 && !notification.hasCompletedProgress(); 271 if (isProgress) { 272 return mIsGroupNotificationAdapter 273 ? NotificationViewType.PROGRESS_IN_GROUP : NotificationViewType.PROGRESS; 274 } 275 276 // inbox 277 boolean isInbox = extras.containsKey(Notification.EXTRA_TITLE_BIG) 278 && extras.containsKey(Notification.EXTRA_SUMMARY_TEXT); 279 if (isInbox) { 280 return mIsGroupNotificationAdapter 281 ? NotificationViewType.INBOX_IN_GROUP : NotificationViewType.INBOX; 282 } 283 284 // group summary 285 boolean isGroupSummary = notificationGroup.getChildTitles() != null; 286 if (isGroupSummary) { 287 return NotificationViewType.GROUP_SUMMARY; 288 } 289 290 // the big text and big picture styles are fallen back to basic template in car 291 // i.e. setting the big text and big picture does not have an effect 292 boolean isBigText = extras.containsKey(Notification.EXTRA_BIG_TEXT); 293 if (isBigText) { 294 Log.i(TAG, "Big text style is not supported as a car notification"); 295 } 296 boolean isBigPicture = extras.containsKey(Notification.EXTRA_PICTURE); 297 if (isBigPicture) { 298 Log.i(TAG, "Big picture style is not supported as a car notification"); 299 } 300 301 // basic, big text, big picture 302 return mIsGroupNotificationAdapter 303 ? NotificationViewType.BASIC_IN_GROUP : NotificationViewType.BASIC; 304 } 305 306 @Override getUnrestrictedItemCount()307 public int getUnrestrictedItemCount() { 308 return mNotifications.size(); 309 } 310 311 @Override setMaxItems(int maxItems)312 public void setMaxItems(int maxItems) { 313 if (maxItems == ContentLimitingAdapter.UNLIMITED 314 || (!mHasHeaderAndFooter && !mHasUnseenNotifications && !mHasSeenNotifications)) { 315 mMaxItems = maxItems; 316 } else { 317 // Adding to max limit of notifications for each header so that they do not count 318 // towards limit. 319 // Footer is not accounted for since it as the end of the list and it doesn't affect the 320 // limit of notifications above it. 321 mMaxItems = maxItems; 322 if (mHasHeaderAndFooter) { 323 mMaxItems++; 324 } 325 if (mHasSeenNotifications) { 326 mMaxItems++; 327 } 328 if (mHasUnseenNotifications) { 329 mMaxItems++; 330 } 331 } 332 super.setMaxItems(mMaxItems); 333 } 334 335 @Override getScrollToPositionWhenRestricted()336 protected int getScrollToPositionWhenRestricted() { 337 if (mLayoutManager == null) { 338 return -1; 339 } 340 int firstItem = mLayoutManager.findFirstVisibleItemPosition(); 341 if (firstItem >= getItemCount() - 1) { 342 return getItemCount() - 1; 343 } 344 return -1; 345 } 346 347 @Override getItemId(int position)348 public long getItemId(int position) { 349 NotificationGroup notificationGroup = mNotifications.get(position); 350 if (notificationGroup.isHeader()) { 351 return ID_HEADER; 352 } 353 if (mShowRecentsAndOlderHeaders && !mIsGroupNotificationAdapter) { 354 if (notificationGroup.isRecentsHeader()) { 355 return ID_RECENT_HEADER; 356 } 357 if (notificationGroup.isOlderHeader()) { 358 return ID_OLDER_HEADER; 359 } 360 if (notificationGroup.isFooter()) { 361 return ID_FOOTER; 362 } 363 } 364 if (notificationGroup.isFooter()) { 365 // We can use recent header's ID when it isn't being used. 366 return ID_RECENT_HEADER; 367 } 368 369 String key = notificationGroup.isGroup() 370 ? notificationGroup.getGroupKey() 371 : notificationGroup.getSingleNotification().getKey(); 372 373 if (mShowRecentsAndOlderHeaders) { 374 key += notificationGroup.isSeen(); 375 } 376 377 return key.hashCode(); 378 } 379 380 /** 381 * Set the expansion state of a group notification given its group key. 382 * 383 * @param groupKey the unique identifier of a {@link NotificationGroup} 384 * @param isSeen whether the {@link NotificationGroup} has been seen by the user 385 * @param isExpanded whether the group notification should be expanded. 386 */ setExpanded(String groupKey, boolean isSeen, boolean isExpanded)387 public void setExpanded(String groupKey, boolean isSeen, boolean isExpanded) { 388 if (isExpanded(groupKey, isSeen) == isExpanded) { 389 return; 390 } 391 392 ExpandedNotification expandedNotification = new ExpandedNotification(groupKey, isSeen); 393 if (isExpanded) { 394 mExpandedNotifications.add(expandedNotification); 395 } else { 396 mExpandedNotifications.remove(expandedNotification); 397 } 398 if (DEBUG) { 399 Log.d(TAG, "Expanded notification statuses: " + mExpandedNotifications); 400 } 401 } 402 403 /** 404 * Collapses all expanded groups. 405 */ collapseAllGroups()406 public void collapseAllGroups() { 407 if (!mExpandedNotifications.isEmpty()) { 408 mExpandedNotifications.clear(); 409 } 410 } 411 412 /** 413 * Returns whether the notification is expanded given its group key and it's seen status. 414 * 415 * @param groupKey the unique identifier of a {@link NotificationGroup} 416 * @param isSeen whether the {@link NotificationGroup} has been seen by the user 417 */ isExpanded(String groupKey, boolean isSeen)418 boolean isExpanded(String groupKey, boolean isSeen) { 419 ExpandedNotification expandedNotification = new ExpandedNotification(groupKey, isSeen); 420 return mExpandedNotifications.contains(expandedNotification); 421 } 422 423 /** 424 * Gets the current {@link CarUxRestrictions}. 425 */ getCarUxRestrictions()426 public CarUxRestrictions getCarUxRestrictions() { 427 return mCarUxRestrictions; 428 } 429 430 /** 431 * Updates notifications and update views. 432 * 433 * @param setRecyclerViewListHeaderAndFooter sets the header and footer on the entire list of 434 * items within the recycler view. This is NOT the header/footer for the grouped notifications. 435 */ setNotifications(List<NotificationGroup> notifications, boolean setRecyclerViewListHeadersAndFooters)436 public void setNotifications(List<NotificationGroup> notifications, 437 boolean setRecyclerViewListHeadersAndFooters) { 438 if (mShowRecentsAndOlderHeaders && !mIsGroupNotificationAdapter) { 439 List<NotificationGroup> seenNotifications = new ArrayList<>(); 440 List<NotificationGroup> unseenNotifications = new ArrayList<>(); 441 notifications.forEach(notificationGroup -> { 442 if (notificationGroup.isSeen()) { 443 seenNotifications.add(notificationGroup); 444 } else { 445 unseenNotifications.add(notificationGroup); 446 } 447 }); 448 setSeenAndUnseenNotifications(unseenNotifications, seenNotifications, 449 setRecyclerViewListHeadersAndFooters); 450 return; 451 } 452 453 List<NotificationGroup> notificationGroupList = new ArrayList<>(notifications); 454 455 if (setRecyclerViewListHeadersAndFooters) { 456 // add header as the first item of the list. 457 notificationGroupList.add(0, createNotificationHeader()); 458 // add footer as the last item of the list. 459 notificationGroupList.add(createNotificationFooter()); 460 mHasHeaderAndFooter = true; 461 } else { 462 mHasHeaderAndFooter = false; 463 } 464 465 CarNotificationDiff notificationDiff = 466 new CarNotificationDiff(mContext, mNotifications, notificationGroupList, mMaxItems); 467 notificationDiff.setShowRecentsAndOlderHeaders(false); 468 DiffUtil.DiffResult diffResult = 469 DiffUtil.calculateDiff(notificationDiff, /* detectMoves= */ false); 470 mNotifications = notificationGroupList; 471 if (DEBUG) { 472 Log.d(TAG, "Updated adapter view holders: " + mNotifications); 473 } 474 updateUnderlyingDataChanged(getUnrestrictedItemCount(), /* newAnchorIndex= */ 0); 475 diffResult.dispatchUpdatesTo(this); 476 } 477 setSeenAndUnseenNotifications(List<NotificationGroup> unseenNotifications, List<NotificationGroup> seenNotifications, boolean setRecyclerViewListHeadersAndFooters)478 private void setSeenAndUnseenNotifications(List<NotificationGroup> unseenNotifications, 479 List<NotificationGroup> seenNotifications, 480 boolean setRecyclerViewListHeadersAndFooters) { 481 if (DEBUG) { 482 Log.d(TAG, "Seen notifications: " + seenNotifications); 483 Log.d(TAG, "Unseen notifications: " + unseenNotifications); 484 } 485 486 List<NotificationGroup> notificationGroupList; 487 if (unseenNotifications.isEmpty()) { 488 mHasUnseenNotifications = false; 489 490 notificationGroupList = new ArrayList<>(); 491 } else { 492 mHasUnseenNotifications = true; 493 494 notificationGroupList = new ArrayList<>(unseenNotifications); 495 if (setRecyclerViewListHeadersAndFooters) { 496 // Add recents header as the first item of the list. 497 notificationGroupList.add(/* index= */ 0, createRecentsHeader()); 498 } 499 } 500 501 if (seenNotifications.isEmpty()) { 502 mHasSeenNotifications = false; 503 } else { 504 mHasSeenNotifications = true; 505 506 if (setRecyclerViewListHeadersAndFooters) { 507 // Append older header to the list. 508 notificationGroupList.add(createOlderHeader()); 509 } 510 notificationGroupList.addAll(seenNotifications); 511 } 512 513 if (setRecyclerViewListHeadersAndFooters) { 514 // Add header as the first item of the list. 515 notificationGroupList.add(0, createNotificationHeader()); 516 // Add footer as the last item of the list. 517 notificationGroupList.add(createNotificationFooter()); 518 mHasHeaderAndFooter = true; 519 } else { 520 mHasHeaderAndFooter = false; 521 } 522 523 CarNotificationDiff notificationDiff = 524 new CarNotificationDiff(mContext, mNotifications, notificationGroupList, mMaxItems); 525 notificationDiff.setShowRecentsAndOlderHeaders(true); 526 DiffUtil.DiffResult diffResult = 527 DiffUtil.calculateDiff(notificationDiff, /* detectMoves= */ false); 528 mNotifications = notificationGroupList; 529 if (DEBUG) { 530 Log.d(TAG, "Updated adapter view holders: " + mNotifications); 531 } 532 updateUnderlyingDataChanged(getUnrestrictedItemCount(), /* newAnchorIndex= */ 0); 533 diffResult.dispatchUpdatesTo(this); 534 } 535 536 /** 537 * Returns {@code true} if notifications are present in adapter. 538 * 539 * Group notification list doesn't have any headers, hence, if there are any notifications 540 * present the size will be more than zero. 541 * 542 * Non-group notification list has header and footer by default. Therefore the min number of 543 * items in the adapter will always be two. If there are any notifications present the size will 544 * be more than two. 545 * 546 * When recent and older headers are enabled, each header will be accounted for when checking 547 * for the presence of notifications. 548 */ hasNotifications()549 public boolean hasNotifications() { 550 int numberOfHeaders; 551 if (mIsGroupNotificationAdapter) { 552 numberOfHeaders = 0; 553 } else { 554 numberOfHeaders = 2; 555 556 if (mHasSeenNotifications) { 557 numberOfHeaders++; 558 } 559 560 if (mHasUnseenNotifications) { 561 numberOfHeaders++; 562 } 563 } 564 565 return getItemCount() > numberOfHeaders; 566 } 567 createNotificationHeader()568 private NotificationGroup createNotificationHeader() { 569 NotificationGroup notificationGroupWithHeader = new NotificationGroup(); 570 notificationGroupWithHeader.setHeader(true); 571 notificationGroupWithHeader.setGroupKey("notification_header"); 572 return notificationGroupWithHeader; 573 } 574 createNotificationFooter()575 private NotificationGroup createNotificationFooter() { 576 NotificationGroup notificationGroupWithFooter = new NotificationGroup(); 577 notificationGroupWithFooter.setFooter(true); 578 notificationGroupWithFooter.setGroupKey("notification_footer"); 579 return notificationGroupWithFooter; 580 } 581 createRecentsHeader()582 private NotificationGroup createRecentsHeader() { 583 NotificationGroup notificationGroupWithRecents = new NotificationGroup(); 584 notificationGroupWithRecents.setRecentsHeader(true); 585 notificationGroupWithRecents.setGroupKey("notification_recents"); 586 notificationGroupWithRecents.setSeen(false); 587 return notificationGroupWithRecents; 588 } 589 createOlderHeader()590 private NotificationGroup createOlderHeader() { 591 NotificationGroup notificationGroupWithOlder = new NotificationGroup(); 592 notificationGroupWithOlder.setOlderHeader(true); 593 notificationGroupWithOlder.setGroupKey("notification_older"); 594 notificationGroupWithOlder.setSeen(true); 595 return notificationGroupWithOlder; 596 } 597 598 /** Implementation of {@link PreprocessingManager.CallStateListener} **/ 599 @Override onCallStateChanged(boolean isInCall)600 public void onCallStateChanged(boolean isInCall) { 601 if (isInCall != mIsInCall) { 602 mIsInCall = isInCall; 603 notifyDataSetChanged(); 604 } 605 } 606 607 /** 608 * Sets the current {@link CarUxRestrictions}. 609 */ setCarUxRestrictions(CarUxRestrictions carUxRestrictions)610 public void setCarUxRestrictions(CarUxRestrictions carUxRestrictions) { 611 Log.d(TAG, "setCarUxRestrictions"); 612 mCarUxRestrictions = carUxRestrictions; 613 notifyDataSetChanged(); 614 } 615 616 /** 617 * Helper method that determines whether a notification is a messaging notification and 618 * should have restricted content (no message preview). 619 */ shouldRestrictMessagePreview()620 private boolean shouldRestrictMessagePreview() { 621 return mCarUxRestrictions != null && (mCarUxRestrictions.getActiveRestrictions() 622 & CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE) != 0; 623 } 624 625 /** 626 * Get root recycler view's view pool so that the child recycler view can share the same 627 * view pool with the parent. 628 */ getViewPool()629 public RecyclerView.RecycledViewPool getViewPool() { 630 if (mIsGroupNotificationAdapter) { 631 // currently only support one level of expansion. 632 throw new IllegalStateException("CarNotificationViewAdapter is a child adapter; " 633 + "its view pool should not be reused."); 634 } 635 return mViewPool; 636 } 637 638 /** 639 * Sets the NotificationClickHandlerFactory that allows for a hook to run a block off code 640 * when the notification is clicked. This is useful to dismiss a screen after 641 * a notification list clicked. 642 */ setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory)643 public void setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory) { 644 mClickHandlerFactory = clickHandlerFactory; 645 } 646 647 /** 648 * Set notification groups as seen. 649 * 650 * @param start Initial adapter position of the notification groups. 651 * @param end Final adapter position of the notification groups. 652 */ setNotificationsAsSeen(int start, int end)653 /* package */ void setNotificationsAsSeen(int start, int end) { 654 if (mNotificationDataManager == null || mIsGroupNotificationAdapter) { 655 return; 656 } 657 658 start = Math.max(start, 0); 659 end = Math.min(end, mNotifications.size() - 1); 660 661 List<AlertEntry> notifications = new ArrayList(); 662 for (int i = start; i <= end; i++) { 663 NotificationGroup group = mNotifications.get(i); 664 AlertEntry groupSummary = group.getGroupSummaryNotification(); 665 if (groupSummary != null) { 666 notifications.add(groupSummary); 667 } 668 669 notifications.addAll(group.getChildNotifications()); 670 } 671 672 mNotificationDataManager.setNotificationsAsSeen(notifications); 673 } 674 675 @Override getConfigurationId()676 public int getConfigurationId() { 677 return R.id.notification_list_uxr_config; 678 } 679 680 private static class ExpandedNotification { 681 private String mKey; 682 private boolean mIsExpanded; 683 ExpandedNotification(String key, boolean isExpanded)684 ExpandedNotification(String key, boolean isExpanded) { 685 mKey = key; 686 mIsExpanded = isExpanded; 687 } 688 689 @Override equals(Object obj)690 public boolean equals(Object obj) { 691 if (!(obj instanceof ExpandedNotification)) { 692 return false; 693 } 694 ExpandedNotification other = (ExpandedNotification) obj; 695 696 return mKey.equals(other.getKey()) && mIsExpanded == other.isExpanded(); 697 } 698 getKey()699 public String getKey() { 700 return mKey; 701 } 702 isExpanded()703 public boolean isExpanded() { 704 return mIsExpanded; 705 } 706 } 707 } 708