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