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 final CarUxRestrictionManagerWrapper mUxResitrictionListener;
25     private final NotificationDataManager mNotificationDataManager;
26     private final NotificationUpdateHandler mNotificationUpdateHandler =
27             new NotificationUpdateHandler();
28     private boolean mShowLessImportantNotifications;
29     private boolean mIsVisible;
30 
NotificationViewController(CarNotificationView carNotificationView, PreprocessingManager preprocessingManager, CarNotificationListener carNotificationListener, CarUxRestrictionManagerWrapper uxResitrictionListener)31     public NotificationViewController(CarNotificationView carNotificationView,
32             PreprocessingManager preprocessingManager,
33             CarNotificationListener carNotificationListener,
34             CarUxRestrictionManagerWrapper uxResitrictionListener) {
35         mCarNotificationView = carNotificationView;
36         mPreprocessingManager = preprocessingManager;
37         mCarNotificationListener = carNotificationListener;
38         mUxResitrictionListener = uxResitrictionListener;
39         mNotificationDataManager = NotificationDataManager.getInstance();
40         mShowRecentsAndOlderHeaders = mCarNotificationView.getContext()
41                         .getResources().getBoolean(R.bool.config_showRecentAndOldHeaders);
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         resetNotifications(mShowLessImportantNotifications);
61     }
62 
63     /**
64      * Updates UI and registers required listeners
65      */
enable()66     public void enable() {
67         mCarNotificationListener.setHandler(mNotificationUpdateHandler);
68         mUxResitrictionListener.setCarNotificationView(mCarNotificationView);
69         try {
70             CarUxRestrictions currentRestrictions =
71                     mUxResitrictionListener.getCurrentCarUxRestrictions();
72             mCarNotificationView.onUxRestrictionsChanged(currentRestrictions);
73         } catch (RuntimeException e) {
74             Log.e(TAG, "Car not connected", e);
75         }
76     }
77 
78     /**
79      * Remove listeners.
80      */
disable()81     public void disable() {
82         mCarNotificationListener.setHandler(null);
83         mUxResitrictionListener.setCarNotificationView(null);
84     }
85 
86     /**
87      * Called when the notification view's visibility is changed.
88      */
onVisibilityChanged(boolean isVisible)89     public void onVisibilityChanged(boolean isVisible) {
90         mIsVisible = isVisible;
91         mCarNotificationListener.onVisibilityChanged(mIsVisible);
92         // Reset and collapse all groups when notification view disappears.
93         if (!mIsVisible) {
94             resetNotifications(mShowLessImportantNotifications);
95             mCarNotificationView.resetState();
96         }
97     }
98 
99     /**
100      * Reset notifications to the latest state.
101      */
resetNotifications(boolean showLessImportantNotifications)102     private void resetNotifications(boolean showLessImportantNotifications) {
103         mPreprocessingManager.init(mCarNotificationListener.getNotifications(),
104                 mCarNotificationListener.getCurrentRanking());
105 
106         if (DEBUG) {
107             Log.d(TAG, "Unprocessed notification map: "
108                     + mCarNotificationListener.getNotifications());
109         }
110 
111         List<NotificationGroup> notificationGroups = mPreprocessingManager.process(
112                 showLessImportantNotifications,
113                 mCarNotificationListener.getNotifications(),
114                 mCarNotificationListener.getCurrentRanking());
115 
116         if (DEBUG) {
117             Log.d(TAG, "Processed notification groups: " + notificationGroups);
118         }
119 
120         if (!mShowRecentsAndOlderHeaders) {
121             mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
122         }
123 
124         mCarNotificationView.setNotifications(notificationGroups);
125     }
126 
127     /**
128      * Update notifications: no grouping/ranking updates will go through.
129      * Insertion, deletion and content update will apply immediately.
130      */
updateNotifications( boolean showLessImportantNotifications, int what, AlertEntry alertEntry)131     private void updateNotifications(
132             boolean showLessImportantNotifications, int what, AlertEntry alertEntry) {
133 
134         if (mPreprocessingManager.shouldFilter(alertEntry,
135                 mCarNotificationListener.getCurrentRanking())) {
136             // if the new notification should be filtered out, return early
137             return;
138         }
139 
140         List<NotificationGroup> notificationGroups = mPreprocessingManager.updateNotifications(
141                 showLessImportantNotifications,
142                 alertEntry,
143                 what,
144                 mCarNotificationListener.getCurrentRanking());
145         if (what == CarNotificationListener.NOTIFY_NOTIFICATION_REMOVED) {
146             mCarNotificationView.removeNotification(alertEntry);
147         } else {
148             if (DEBUG) {
149                 Log.d(TAG, "Updated notification groups: " + notificationGroups);
150             }
151 
152             mCarNotificationView.setNotifications(notificationGroups);
153         }
154     }
155 
156     private class NotificationUpdateHandler extends Handler {
157         @Override
handleMessage(Message message)158         public void handleMessage(Message message) {
159             if (mIsVisible) {
160                 if (message.what == CarNotificationListener.NOTIFY_RANKING_UPDATED) {
161                     // Do not update notifications if ranking is updated while panel is visible.
162                     return;
163                 }
164 
165                 updateNotifications(
166                         mShowLessImportantNotifications,
167                         message.what,
168                         (AlertEntry) message.obj);
169             } else {
170                 resetNotifications(mShowLessImportantNotifications);
171             }
172         }
173     }
174 }
175