package com.android.car.notification; import android.car.CarNotConnectedException; import android.car.drivingstate.CarUxRestrictions; import android.os.Build; import android.os.Handler; import android.os.Message; import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.View; import android.widget.Toast; import java.util.List; import java.util.stream.Collectors; /** * This class is a bridge to collect signals from the notification and ux restriction services and * trigger the correct UI updates. */ public class NotificationViewController { private static final String TAG = "NotificationViewControl"; private final CarNotificationView mCarNotificationView; private final PreprocessingManager mPreprocessingManager; private final CarNotificationListener mCarNotificationListener; private CarUxRestrictionManagerWrapper mUxResitrictionListener; private NotificationDataManager mNotificationDataManager; private NotificationUpdateHandler mNotificationUpdateHandler = new NotificationUpdateHandler(); private boolean mShowLessImportantNotifications; private boolean mIsInForeground; public NotificationViewController(CarNotificationView carNotificationView, PreprocessingManager preprocessingManager, CarNotificationListener carNotificationListener, CarUxRestrictionManagerWrapper uxResitrictionListener, NotificationDataManager notificationDataManager) { mCarNotificationView = carNotificationView; mPreprocessingManager = preprocessingManager; mCarNotificationListener = carNotificationListener; mUxResitrictionListener = uxResitrictionListener; mNotificationDataManager = notificationDataManager; // Long clicking on the notification center title toggles hiding media, navigation, and // less important (< IMPORTANCE_DEFAULT) ongoing foreground service notifications. // This is only available for ENG and USERDEBUG builds. View view = mCarNotificationView.findViewById(R.id.notification_center_title); if (view != null && (Build.IS_ENG || Build.IS_USERDEBUG)) { view.setOnLongClickListener(v -> { mShowLessImportantNotifications = !mShowLessImportantNotifications; Toast.makeText( carNotificationView.getContext(), "Foreground, navigation and media notifications " + ( mShowLessImportantNotifications ? "ENABLED" : "DISABLED"), Toast.LENGTH_SHORT).show(); resetNotifications(mShowLessImportantNotifications); return true; }); } } /** * Updates UI and registers required listeners */ public void enable() { mCarNotificationListener.setHandler(mNotificationUpdateHandler); mUxResitrictionListener.setCarNotificationView(mCarNotificationView); try { CarUxRestrictions currentRestrictions = mUxResitrictionListener.getCurrentCarUxRestrictions(); mCarNotificationView.onUxRestrictionsChanged(currentRestrictions); } catch (CarNotConnectedException e) { Log.e(TAG, "Car not connected", e); } } /** * Remove listeners. */ public void disable() { mCarNotificationListener.setHandler(null); mUxResitrictionListener.setCarNotificationView(null); } /** * Reset the list view. Called when the notification list is not in the foreground. */ public void setIsInForeground(boolean isInForeground) { mIsInForeground = isInForeground; // Reset and collapse all groups when notification view disappears. if (!mIsInForeground) { resetNotifications(mShowLessImportantNotifications); mCarNotificationView.collapseAllGroups(); } } /** * Reset notifications to the latest state. */ private void resetNotifications(boolean showLessImportantNotifications) { mPreprocessingManager.init( mCarNotificationListener.getNotifications(), mCarNotificationListener.getCurrentRanking()); List notificationGroups = mPreprocessingManager.process( showLessImportantNotifications, mCarNotificationListener.getNotifications(), mCarNotificationListener.getCurrentRanking()); List unseenNotifications = notificationGroups.stream() .filter(g -> g.getChildNotifications().stream() .anyMatch(mCarNotificationListener::shouldTrackUnseen)) .collect(Collectors.toList()); mNotificationDataManager.updateUnseenNotification(unseenNotifications); mCarNotificationView.setNotifications(notificationGroups); } /** * Update notifications: no grouping/ranking updates will go through. * Insertion, deletion and content update will apply immediately. */ private void updateNotifications( boolean showLessImportantNotifications, int what, StatusBarNotification sbn) { if (mPreprocessingManager.shouldFilter(sbn, mCarNotificationListener.getCurrentRanking())) { // if the new notification should be filtered out, return early return; } mCarNotificationView.setNotifications( mPreprocessingManager.updateNotifications( showLessImportantNotifications, sbn, what, mCarNotificationListener.getCurrentRanking())); } private class NotificationUpdateHandler extends Handler { @Override public void handleMessage(Message message) { if (mIsInForeground) { updateNotifications( mShowLessImportantNotifications, message.what, (StatusBarNotification) message.obj); } else { resetNotifications(mShowLessImportantNotifications); } } } }