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 
13 /**
14  * This class is a bridge to collect signals from the notification and ux restriction services and
15  * trigger the correct UI updates.
16  */
17 public class NotificationViewController {
18     private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG;
19     private static final String TAG = "NotificationViewControl";
20     private final CarNotificationView mCarNotificationView;
21     private final PreprocessingManager mPreprocessingManager;
22     private final CarNotificationListener mCarNotificationListener;
23     private final boolean mShowRecentsAndOlderHeaders;
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)30     public NotificationViewController(CarNotificationView carNotificationView,
31             PreprocessingManager preprocessingManager,
32             CarNotificationListener carNotificationListener,
33             CarUxRestrictionManagerWrapper uxResitrictionListener) {
34         mCarNotificationView = carNotificationView;
35         mPreprocessingManager = preprocessingManager;
36         mCarNotificationListener = carNotificationListener;
37         mUxResitrictionListener = uxResitrictionListener;
38         mNotificationDataManager = NotificationDataManager.getInstance();
39         mShowRecentsAndOlderHeaders = mCarNotificationView.getContext()
40                         .getResources().getBoolean(R.bool.config_showRecentAndOldHeaders);
41 
42         // Long clicking on the notification center title toggles hiding media, navigation, and
43         // less important (< IMPORTANCE_DEFAULT) ongoing foreground service notifications.
44         // This is only available for ENG and USERDEBUG builds.
45         View view = mCarNotificationView.findViewById(R.id.notification_center_title);
46         if (view != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
47             view.setOnLongClickListener(v -> {
48                 mShowLessImportantNotifications = !mShowLessImportantNotifications;
49                 Toast.makeText(
50                         carNotificationView.getContext(),
51                         "Foreground, navigation and media notifications " + (
52                                 mShowLessImportantNotifications ? "ENABLED" : "DISABLED"),
53                         Toast.LENGTH_SHORT).show();
54                 resetNotifications(mShowLessImportantNotifications);
55                 return true;
56             });
57         }
58 
59         resetNotifications(mShowLessImportantNotifications);
60     }
61 
62     /**
63      * Updates UI and registers required listeners
64      */
enable()65     public void enable() {
66         mCarNotificationListener.setHandler(mNotificationUpdateHandler);
67         mUxResitrictionListener.setCarNotificationView(mCarNotificationView);
68         try {
69             CarUxRestrictions currentRestrictions =
70                     mUxResitrictionListener.getCurrentCarUxRestrictions();
71             mCarNotificationView.onUxRestrictionsChanged(currentRestrictions);
72         } catch (RuntimeException e) {
73             Log.e(TAG, "Car not connected", e);
74         }
75     }
76 
77     /**
78      * Remove listeners.
79      */
disable()80     public void disable() {
81         mCarNotificationListener.setHandler(null);
82         mUxResitrictionListener.setCarNotificationView(null);
83     }
84 
85     /**
86      * Called when the notification view's visibility is changed.
87      */
onVisibilityChanged(boolean isVisible)88     public void onVisibilityChanged(boolean isVisible) {
89         mIsVisible = isVisible;
90         // Reset and collapse all groups when notification view disappears.
91         if (!mIsVisible) {
92             resetNotifications(mShowLessImportantNotifications);
93             mCarNotificationView.resetState();
94         }
95     }
96 
97     /**
98      * Reset notifications to the latest state.
99      */
resetNotifications(boolean showLessImportantNotifications)100     private void resetNotifications(boolean showLessImportantNotifications) {
101         mPreprocessingManager.init(mCarNotificationListener.getNotifications(),
102                 mCarNotificationListener.getCurrentRanking());
103 
104         if (DEBUG) {
105             Log.d(TAG, "Unprocessed notification map: "
106                     + mCarNotificationListener.getNotifications());
107         }
108 
109         List<NotificationGroup> notificationGroups = mPreprocessingManager.process(
110                 showLessImportantNotifications,
111                 mCarNotificationListener.getNotifications(),
112                 mCarNotificationListener.getCurrentRanking());
113 
114         if (DEBUG) {
115             Log.d(TAG, "Processed notification groups: " + notificationGroups);
116         }
117 
118         if (!mShowRecentsAndOlderHeaders) {
119             mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
120         }
121 
122         mCarNotificationView.setNotifications(notificationGroups);
123     }
124 
125     /**
126      * Update notifications: no grouping/ranking updates will go through.
127      * Insertion, deletion and content update will apply immediately.
128      */
updateNotifications( boolean showLessImportantNotifications, int what, AlertEntry alertEntry)129     private void updateNotifications(
130             boolean showLessImportantNotifications, int what, AlertEntry alertEntry) {
131 
132         if (mPreprocessingManager.shouldFilter(alertEntry,
133                 mCarNotificationListener.getCurrentRanking())) {
134             // if the new notification should be filtered out, return early
135             return;
136         }
137 
138         List<NotificationGroup> notificationGroups = mPreprocessingManager.updateNotifications(
139                 showLessImportantNotifications,
140                 alertEntry,
141                 what,
142                 mCarNotificationListener.getCurrentRanking());
143         if (what == CarNotificationListener.NOTIFY_NOTIFICATION_REMOVED) {
144             mCarNotificationView.removeNotification(alertEntry);
145         } else {
146             if (DEBUG) {
147                 Log.d(TAG, "Updated notification groups: " + notificationGroups);
148             }
149 
150             mCarNotificationView.setNotifications(notificationGroups);
151         }
152     }
153 
154     private class NotificationUpdateHandler extends Handler {
155         @Override
handleMessage(Message message)156         public void handleMessage(Message message) {
157             if (mIsVisible) {
158                 if (message.what == CarNotificationListener.NOTIFY_RANKING_UPDATED) {
159                     // Do not update notifications if ranking is updated while panel is visible.
160                     return;
161                 }
162 
163                 updateNotifications(
164                         mShowLessImportantNotifications,
165                         message.what,
166                         (AlertEntry) message.obj);
167             } else {
168                 resetNotifications(mShowLessImportantNotifications);
169             }
170         }
171     }
172 }
173