1 package com.android.car.notification;
2 
3 import android.car.drivingstate.CarUxRestrictions;
4 import android.os.Build;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.util.Log;
8 import android.view.View;
9 import android.widget.Toast;
10 
11 import java.util.List;
12 import java.util.stream.Collectors;
13 
14 /**
15  * This class is a bridge to collect signals from the notification and ux restriction services and
16  * trigger the correct UI updates.
17  */
18 public class NotificationViewController {
19 
20     private static final String TAG = "NotificationViewControl";
21     private final CarNotificationView mCarNotificationView;
22     private final PreprocessingManager mPreprocessingManager;
23     private final CarNotificationListener mCarNotificationListener;
24     private CarUxRestrictionManagerWrapper mUxResitrictionListener;
25     private NotificationDataManager mNotificationDataManager;
26     private NotificationUpdateHandler mNotificationUpdateHandler = new NotificationUpdateHandler();
27     private boolean mShowLessImportantNotifications;
28     private boolean mIsVisible;
29 
NotificationViewController(CarNotificationView carNotificationView, PreprocessingManager preprocessingManager, CarNotificationListener carNotificationListener, CarUxRestrictionManagerWrapper uxResitrictionListener, NotificationDataManager notificationDataManager)30     public NotificationViewController(CarNotificationView carNotificationView,
31             PreprocessingManager preprocessingManager,
32             CarNotificationListener carNotificationListener,
33             CarUxRestrictionManagerWrapper uxResitrictionListener,
34             NotificationDataManager notificationDataManager) {
35         mCarNotificationView = carNotificationView;
36         mPreprocessingManager = preprocessingManager;
37         mCarNotificationListener = carNotificationListener;
38         mUxResitrictionListener = uxResitrictionListener;
39         mNotificationDataManager = notificationDataManager;
40 
41         // Long clicking on the notification center title toggles hiding media, navigation, and
42         // less important (< IMPORTANCE_DEFAULT) ongoing foreground service notifications.
43         // This is only available for ENG and USERDEBUG builds.
44         View view = mCarNotificationView.findViewById(R.id.notification_center_title);
45         if (view != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
46             view.setOnLongClickListener(v -> {
47                 mShowLessImportantNotifications = !mShowLessImportantNotifications;
48                 Toast.makeText(
49                         carNotificationView.getContext(),
50                         "Foreground, navigation and media notifications " + (
51                                 mShowLessImportantNotifications ? "ENABLED" : "DISABLED"),
52                         Toast.LENGTH_SHORT).show();
53                 resetNotifications(mShowLessImportantNotifications);
54                 return true;
55             });
56         }
57 
58         resetNotifications(mShowLessImportantNotifications);
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 (RuntimeException 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      * Called when the notification view's visibility is changed.
86      */
onVisibilityChanged(boolean isVisible)87     public void onVisibilityChanged(boolean isVisible) {
88         mIsVisible = isVisible;
89         // Reset and collapse all groups when notification view disappears.
90         if (!mIsVisible) {
91             resetNotifications(mShowLessImportantNotifications);
92             mCarNotificationView.resetState();
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, AlertEntry alertEntry)122     private void updateNotifications(
123             boolean showLessImportantNotifications, int what, AlertEntry alertEntry) {
124 
125         if (mPreprocessingManager.shouldFilter(alertEntry,
126                 mCarNotificationListener.getCurrentRanking())) {
127             // if the new notification should be filtered out, return early
128             return;
129         }
130 
131         mCarNotificationView.setNotifications(
132                 mPreprocessingManager.updateNotifications(
133                         showLessImportantNotifications,
134                         alertEntry,
135                         what,
136                         mCarNotificationListener.getCurrentRanking()));
137     }
138 
139     private class NotificationUpdateHandler extends Handler {
140         @Override
handleMessage(Message message)141         public void handleMessage(Message message) {
142             if (mIsVisible) {
143                 updateNotifications(
144                         mShowLessImportantNotifications,
145                         message.what,
146                         (AlertEntry) message.obj);
147             } else {
148                 resetNotifications(mShowLessImportantNotifications);
149             }
150         }
151     }
152 }
153