1 package com.android.car.notification; 2 3 import android.car.CarNotConnectedException; 4 import android.car.drivingstate.CarUxRestrictions; 5 import android.os.Build; 6 import android.os.Handler; 7 import android.os.Message; 8 import android.service.notification.StatusBarNotification; 9 import android.util.Log; 10 import android.view.View; 11 import android.widget.Toast; 12 13 import java.util.List; 14 import java.util.stream.Collectors; 15 16 /** 17 * This class is a bridge to collect signals from the notification and ux restriction services and 18 * trigger the correct UI updates. 19 */ 20 public class NotificationViewController { 21 22 private static final String TAG = "NotificationViewControl"; 23 private final CarNotificationView mCarNotificationView; 24 private final PreprocessingManager mPreprocessingManager; 25 private final CarNotificationListener mCarNotificationListener; 26 private CarUxRestrictionManagerWrapper mUxResitrictionListener; 27 private NotificationDataManager mNotificationDataManager; 28 private NotificationUpdateHandler mNotificationUpdateHandler = new NotificationUpdateHandler(); 29 private boolean mShowLessImportantNotifications; 30 private boolean mIsInForeground; 31 NotificationViewController(CarNotificationView carNotificationView, PreprocessingManager preprocessingManager, CarNotificationListener carNotificationListener, CarUxRestrictionManagerWrapper uxResitrictionListener, NotificationDataManager notificationDataManager)32 public NotificationViewController(CarNotificationView carNotificationView, 33 PreprocessingManager preprocessingManager, 34 CarNotificationListener carNotificationListener, 35 CarUxRestrictionManagerWrapper uxResitrictionListener, 36 NotificationDataManager notificationDataManager) { 37 mCarNotificationView = carNotificationView; 38 mPreprocessingManager = preprocessingManager; 39 mCarNotificationListener = carNotificationListener; 40 mUxResitrictionListener = uxResitrictionListener; 41 mNotificationDataManager = notificationDataManager; 42 43 // Long clicking on the notification center title toggles hiding media, navigation, and 44 // less important (< IMPORTANCE_DEFAULT) ongoing foreground service notifications. 45 // This is only available for ENG and USERDEBUG builds. 46 View view = mCarNotificationView.findViewById(R.id.notification_center_title); 47 if (view != null && (Build.IS_ENG || Build.IS_USERDEBUG)) { 48 view.setOnLongClickListener(v -> { 49 mShowLessImportantNotifications = !mShowLessImportantNotifications; 50 Toast.makeText( 51 carNotificationView.getContext(), 52 "Foreground, navigation and media notifications " + ( 53 mShowLessImportantNotifications ? "ENABLED" : "DISABLED"), 54 Toast.LENGTH_SHORT).show(); 55 resetNotifications(mShowLessImportantNotifications); 56 return true; 57 }); 58 } 59 } 60 61 /** 62 * Updates UI and registers required listeners 63 */ enable()64 public void enable() { 65 mCarNotificationListener.setHandler(mNotificationUpdateHandler); 66 mUxResitrictionListener.setCarNotificationView(mCarNotificationView); 67 try { 68 CarUxRestrictions currentRestrictions = 69 mUxResitrictionListener.getCurrentCarUxRestrictions(); 70 mCarNotificationView.onUxRestrictionsChanged(currentRestrictions); 71 } catch (CarNotConnectedException e) { 72 Log.e(TAG, "Car not connected", e); 73 } 74 } 75 76 /** 77 * Remove listeners. 78 */ disable()79 public void disable() { 80 mCarNotificationListener.setHandler(null); 81 mUxResitrictionListener.setCarNotificationView(null); 82 } 83 84 /** 85 * Reset the list view. Called when the notification list is not in the foreground. 86 */ setIsInForeground(boolean isInForeground)87 public void setIsInForeground(boolean isInForeground) { 88 mIsInForeground = isInForeground; 89 // Reset and collapse all groups when notification view disappears. 90 if (!mIsInForeground) { 91 resetNotifications(mShowLessImportantNotifications); 92 mCarNotificationView.collapseAllGroups(); 93 } 94 } 95 96 /** 97 * Reset notifications to the latest state. 98 */ resetNotifications(boolean showLessImportantNotifications)99 private void resetNotifications(boolean showLessImportantNotifications) { 100 mPreprocessingManager.init( 101 mCarNotificationListener.getNotifications(), 102 mCarNotificationListener.getCurrentRanking()); 103 104 List<NotificationGroup> notificationGroups = mPreprocessingManager.process( 105 showLessImportantNotifications, 106 mCarNotificationListener.getNotifications(), 107 mCarNotificationListener.getCurrentRanking()); 108 109 List<NotificationGroup> unseenNotifications = notificationGroups.stream() 110 .filter(g -> g.getChildNotifications().stream() 111 .anyMatch(mCarNotificationListener::shouldTrackUnseen)) 112 .collect(Collectors.toList()); 113 114 mNotificationDataManager.updateUnseenNotification(unseenNotifications); 115 mCarNotificationView.setNotifications(notificationGroups); 116 } 117 118 /** 119 * Update notifications: no grouping/ranking updates will go through. 120 * Insertion, deletion and content update will apply immediately. 121 */ updateNotifications( boolean showLessImportantNotifications, int what, StatusBarNotification sbn)122 private void updateNotifications( 123 boolean showLessImportantNotifications, int what, StatusBarNotification sbn) { 124 125 if (mPreprocessingManager.shouldFilter(sbn, mCarNotificationListener.getCurrentRanking())) { 126 // if the new notification should be filtered out, return early 127 return; 128 } 129 130 mCarNotificationView.setNotifications( 131 mPreprocessingManager.updateNotifications( 132 showLessImportantNotifications, 133 sbn, 134 what, 135 mCarNotificationListener.getCurrentRanking())); 136 } 137 138 private class NotificationUpdateHandler extends Handler { 139 @Override handleMessage(Message message)140 public void handleMessage(Message message) { 141 if (mIsInForeground) { 142 updateNotifications( 143 mShowLessImportantNotifications, 144 message.what, 145 (StatusBarNotification) message.obj); 146 } else { 147 resetNotifications(mShowLessImportantNotifications); 148 } 149 } 150 } 151 } 152