1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.notification;
17 
18 import static com.android.car.assist.client.CarAssistUtils.isCarCompatibleMessagingNotification;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.app.KeyguardManager;
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.app.NotificationManager;
27 import android.car.drivingstate.CarUxRestrictions;
28 import android.car.drivingstate.CarUxRestrictionsManager;
29 import android.content.Context;
30 import android.service.notification.NotificationListenerService;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewTreeObserver;
35 
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
39 import com.android.car.notification.headsup.animationhelper.HeadsUpNotificationAnimationHelper;
40 import com.android.car.notification.template.MessageNotificationViewHolder;
41 
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 
47 /**
48  * Notification Manager for heads-up notifications in car.
49  */
50 public class CarHeadsUpNotificationManager
51         implements CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
52 
53     /**
54      * Callback that will be issued after a Heads up notification state is changed.
55      */
56     public interface OnHeadsUpNotificationStateChange {
57         /**
58          * Will be called if a new notification added/updated changes the heads up state for that
59          * notification.
60          */
onStateChange(AlertEntry alertEntry, boolean isHeadsUp)61         void onStateChange(AlertEntry alertEntry, boolean isHeadsUp);
62     }
63 
64     private static final String TAG = CarHeadsUpNotificationManager.class.getSimpleName();
65 
66     private final Beeper mBeeper;
67     private final Context mContext;
68     private final boolean mEnableNavigationHeadsup;
69     private final long mDuration;
70     private final long mMinDisplayDuration;
71     private HeadsUpNotificationAnimationHelper mAnimationHelper;
72     private final int mNotificationHeadsUpCardMarginTop;
73 
74     private final KeyguardManager mKeyguardManager;
75     private final PreprocessingManager mPreprocessingManager;
76     private final LayoutInflater mInflater;
77     private final CarHeadsUpNotificationContainer mHunContainer;
78 
79     // key for the map is the statusbarnotification key
80     private final Map<String, HeadsUpEntry> mActiveHeadsUpNotifications = new HashMap<>();
81     private final List<OnHeadsUpNotificationStateChange> mListeners = new ArrayList<>();
82 
83     private boolean mShouldRestrictMessagePreview;
84     private NotificationClickHandlerFactory mClickHandlerFactory;
85     private NotificationDataManager mNotificationDataManager;
86 
87 
CarHeadsUpNotificationManager(Context context, NotificationClickHandlerFactory clickHandlerFactory, NotificationDataManager notificationDataManager, CarHeadsUpNotificationContainer hunContainer)88     public CarHeadsUpNotificationManager(Context context,
89             NotificationClickHandlerFactory clickHandlerFactory,
90             NotificationDataManager notificationDataManager,
91             CarHeadsUpNotificationContainer hunContainer) {
92         mContext = context.getApplicationContext();
93         mEnableNavigationHeadsup =
94                 context.getResources().getBoolean(R.bool.config_showNavigationHeadsup);
95         mClickHandlerFactory = clickHandlerFactory;
96         mNotificationDataManager = notificationDataManager;
97         mBeeper = new Beeper(mContext);
98         mDuration = mContext.getResources().getInteger(R.integer.headsup_notification_duration_ms);
99         mNotificationHeadsUpCardMarginTop = (int) mContext.getResources().getDimension(
100                 R.dimen.headsup_notification_top_margin);
101         mMinDisplayDuration = mContext.getResources().getInteger(
102                 R.integer.heads_up_notification_minimum_time);
103         mAnimationHelper = getAnimationHelper();
104 
105         mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
106         mPreprocessingManager = PreprocessingManager.getInstance(context);
107         mInflater = LayoutInflater.from(mContext);
108         mClickHandlerFactory.registerClickListener(
109                 (launchResult, alertEntry) -> dismissHUN(alertEntry));
110         mHunContainer = hunContainer;
111     }
112 
getAnimationHelper()113     private HeadsUpNotificationAnimationHelper getAnimationHelper() {
114         String helperName = mContext.getResources().getString(
115                 R.string.config_headsUpNotificationAnimationHelper);
116         try {
117             Class<?> clazz = Class.forName(helperName);
118             return (HeadsUpNotificationAnimationHelper) clazz.getConstructor().newInstance();
119         } catch (Exception e) {
120             throw new IllegalArgumentException(
121                     String.format("Invalid animation helper: %s", helperName), e);
122         }
123     }
124 
125     /**
126      * Show the notification as a heads-up if it meets the criteria.
127      *
128      * <p>Return's true if the notification will be shown as a heads up, false otherwise.
129      */
maybeShowHeadsUp( AlertEntry alertEntry, NotificationListenerService.RankingMap rankingMap, Map<String, AlertEntry> activeNotifications)130     public boolean maybeShowHeadsUp(
131             AlertEntry alertEntry,
132             NotificationListenerService.RankingMap rankingMap,
133             Map<String, AlertEntry> activeNotifications) {
134         if (!shouldShowHeadsUp(alertEntry, rankingMap)) {
135             // check if this is an update to the existing notification and if it should still show
136             // as a heads up or not.
137             HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
138                     alertEntry.getKey());
139             if (currentActiveHeadsUpNotification == null) {
140                 return false;
141             }
142             if (CarNotificationDiff.sameNotificationKey(currentActiveHeadsUpNotification,
143                     alertEntry)
144                     && currentActiveHeadsUpNotification.getHandler().hasMessagesOrCallbacks()) {
145                 dismissHUN(alertEntry);
146             }
147             return false;
148         }
149         if (!activeNotifications.containsKey(alertEntry.getKey()) || canUpdate(alertEntry)
150                 || alertAgain(alertEntry.getNotification())) {
151             showHeadsUp(mPreprocessingManager.optimizeForDriving(alertEntry),
152                     rankingMap);
153             return true;
154         }
155         return false;
156     }
157 
158     /**
159      * This method gets called when an app wants to cancel or withdraw its notification.
160      */
maybeRemoveHeadsUp(AlertEntry alertEntry)161     public void maybeRemoveHeadsUp(AlertEntry alertEntry) {
162         HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
163                 alertEntry.getKey());
164         // if the heads up notification is already removed do nothing.
165         if (currentActiveHeadsUpNotification == null) {
166             return;
167         }
168 
169         long totalDisplayDuration =
170                 System.currentTimeMillis() - currentActiveHeadsUpNotification.getPostTime();
171         // ongoing notification that has passed the minimum threshold display time.
172         if (totalDisplayDuration >= mMinDisplayDuration) {
173             removeHUN(alertEntry);
174             return;
175         }
176 
177         long earliestRemovalTime = mMinDisplayDuration - totalDisplayDuration;
178 
179         currentActiveHeadsUpNotification.getHandler().postDelayed(() ->
180                 removeHUN(alertEntry), earliestRemovalTime);
181     }
182 
183     /**
184      * Registers a new {@link OnHeadsUpNotificationStateChange} to the list of listeners.
185      */
registerHeadsUpNotificationStateChangeListener( OnHeadsUpNotificationStateChange listener)186     public void registerHeadsUpNotificationStateChangeListener(
187             OnHeadsUpNotificationStateChange listener) {
188         if (!mListeners.contains(listener)) {
189             mListeners.add(listener);
190         }
191     }
192 
193     /**
194      * Unregisters a {@link OnHeadsUpNotificationStateChange} from the list of listeners.
195      */
unregisterHeadsUpNotificationStateChangeListener( OnHeadsUpNotificationStateChange listener)196     public void unregisterHeadsUpNotificationStateChangeListener(
197             OnHeadsUpNotificationStateChange listener) {
198         mListeners.remove(listener);
199     }
200 
201     /**
202      * Invokes all OnHeadsUpNotificationStateChange handlers registered in {@link
203      * OnHeadsUpNotificationStateChange}s array.
204      */
handleHeadsUpNotificationStateChanged(AlertEntry alertEntry, boolean isHeadsUp)205     private void handleHeadsUpNotificationStateChanged(AlertEntry alertEntry, boolean isHeadsUp) {
206         mListeners.forEach(
207                 listener -> listener.onStateChange(alertEntry, isHeadsUp));
208     }
209 
210     /**
211      * Returns true if the notification's flag is not set to
212      * {@link Notification#FLAG_ONLY_ALERT_ONCE}
213      */
alertAgain(Notification newNotification)214     private boolean alertAgain(Notification newNotification) {
215         return (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
216     }
217 
218     /**
219      * Return true if the currently displaying notification have the same key as the new added
220      * notification. In that case it will be considered as an update to the currently displayed
221      * notification.
222      */
isUpdate(AlertEntry alertEntry)223     private boolean isUpdate(AlertEntry alertEntry) {
224         HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
225                 alertEntry.getKey());
226         if (currentActiveHeadsUpNotification == null) {
227             return false;
228         }
229         return CarNotificationDiff.sameNotificationKey(currentActiveHeadsUpNotification,
230                 alertEntry);
231     }
232 
233     /**
234      * Updates only when the notification is being displayed.
235      */
canUpdate(AlertEntry alertEntry)236     private boolean canUpdate(AlertEntry alertEntry) {
237         HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
238                 alertEntry.getKey());
239         return currentActiveHeadsUpNotification != null && System.currentTimeMillis() -
240                 currentActiveHeadsUpNotification.getPostTime() < mDuration;
241     }
242 
243     /**
244      * Returns the active headsUpEntry or creates a new one while adding it to the list of
245      * mActiveHeadsUpNotifications.
246      */
addNewHeadsUpEntry(AlertEntry alertEntry)247     private HeadsUpEntry addNewHeadsUpEntry(AlertEntry alertEntry) {
248         HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
249                 alertEntry.getKey());
250         if (currentActiveHeadsUpNotification == null) {
251             currentActiveHeadsUpNotification = new HeadsUpEntry(
252                     alertEntry.getStatusBarNotification());
253             handleHeadsUpNotificationStateChanged(alertEntry, /* isHeadsUp= */ true);
254             mActiveHeadsUpNotifications.put(alertEntry.getKey(),
255                     currentActiveHeadsUpNotification);
256             currentActiveHeadsUpNotification.mIsAlertAgain = alertAgain(
257                     alertEntry.getNotification());
258             currentActiveHeadsUpNotification.mIsNewHeadsUp = true;
259             return currentActiveHeadsUpNotification;
260         }
261         currentActiveHeadsUpNotification.mIsNewHeadsUp = false;
262         currentActiveHeadsUpNotification.mIsAlertAgain = alertAgain(
263                 alertEntry.getNotification());
264         if (currentActiveHeadsUpNotification.mIsAlertAgain) {
265             // This is a ongoing notification which needs to be alerted again to the user. This
266             // requires for the post time to be updated.
267             currentActiveHeadsUpNotification.updatePostTime();
268         }
269         return currentActiveHeadsUpNotification;
270     }
271 
272     /**
273      * Controls three major conditions while showing heads up notification.
274      * <p>
275      * <ol>
276      * <li> When a new HUN comes in it will be displayed with animations
277      * <li> If an update to existing HUN comes in which enforces to alert the HUN again to user,
278      * then the post time will be updated to current time. This will only be done if {@link
279      * Notification#FLAG_ONLY_ALERT_ONCE} flag is not set.
280      * <li> If an update to existing HUN comes in which just updates the data and does not want to
281      * alert itself again, then the animations will not be shown and the data will get updated. This
282      * will only be done if {@link Notification#FLAG_ONLY_ALERT_ONCE} flag is not set.
283      * </ol>
284      */
showHeadsUp(AlertEntry alertEntry, NotificationListenerService.RankingMap rankingMap)285     private void showHeadsUp(AlertEntry alertEntry,
286             NotificationListenerService.RankingMap rankingMap) {
287         // Show animations only when there is no active HUN and notification is new. This check
288         // needs to be done here because after this the new notification will be added to the map
289         // holding ongoing notifications.
290         boolean shouldShowAnimation = !isUpdate(alertEntry);
291         HeadsUpEntry currentNotification = addNewHeadsUpEntry(alertEntry);
292         if (currentNotification.mIsNewHeadsUp) {
293             playSound(alertEntry, rankingMap);
294             setAutoDismissViews(currentNotification, alertEntry);
295         } else if (currentNotification.mIsAlertAgain) {
296             setAutoDismissViews(currentNotification, alertEntry);
297         }
298         CarNotificationTypeItem notificationTypeItem = NotificationUtils.getNotificationViewType(
299                 alertEntry);
300         currentNotification.setClickHandlerFactory(mClickHandlerFactory);
301 
302         if (currentNotification.getNotificationView() == null) {
303             currentNotification.setNotificationView(mInflater.inflate(
304                     notificationTypeItem.getHeadsUpTemplate(),
305                     null));
306             mHunContainer.displayNotification(currentNotification.getNotificationView());
307             currentNotification.setViewHolder(
308                     notificationTypeItem.getViewHolder(currentNotification.getNotificationView(),
309                             mClickHandlerFactory));
310         }
311 
312         if (mShouldRestrictMessagePreview && notificationTypeItem.getNotificationType()
313                 == NotificationViewType.MESSAGE) {
314             ((MessageNotificationViewHolder) currentNotification.getViewHolder())
315                     .bindRestricted(alertEntry, /* isInGroup= */ false, /* isHeadsUp= */ true);
316         } else {
317             currentNotification.getViewHolder().bind(alertEntry, /* isInGroup= */false,
318                     /* isHeadsUp= */ true);
319         }
320 
321         // measure the size of the card and make that area of the screen touchable
322         currentNotification.getNotificationView().getViewTreeObserver()
323                 .addOnComputeInternalInsetsListener(
324                         info -> setInternalInsetsInfo(info,
325                                 currentNotification, /* panelExpanded= */false));
326         // Get the height of the notification view after onLayout() in order to animate the
327         // notification into the screen.
328         currentNotification.getNotificationView().getViewTreeObserver().addOnGlobalLayoutListener(
329                 new ViewTreeObserver.OnGlobalLayoutListener() {
330                     @Override
331                     public void onGlobalLayout() {
332                         View view = currentNotification.getNotificationView();
333                         if (shouldShowAnimation) {
334                             mAnimationHelper.resetHUNPosition(view);
335 
336                            AnimatorSet animatorSet = mAnimationHelper.getAnimateInAnimator(mContext, view);
337                            animatorSet.setTarget(view);
338                            animatorSet.start();
339                         }
340                         view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
341                     }
342                 });
343 
344         if (currentNotification.mIsNewHeadsUp) {
345             boolean shouldDismissOnSwipe = true;
346             if (shouldDismissOnSwipe(alertEntry)) {
347                 shouldDismissOnSwipe = false;
348             }
349             // Add swipe gesture
350             View cardView = currentNotification.getNotificationView().findViewById(R.id.card_view);
351             cardView.setOnTouchListener(
352                     new HeadsUpNotificationOnTouchListener(cardView, shouldDismissOnSwipe,
353                             () -> resetView(alertEntry)));
354         }
355     }
356 
setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info, HeadsUpEntry currentNotification, boolean panelExpanded)357     protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
358             HeadsUpEntry currentNotification, boolean panelExpanded) {
359         // If the panel is not on screen don't modify the touch region
360         if (!mHunContainer.isVisible()) return;
361         int[] mTmpTwoArray = new int[2];
362         View cardView = currentNotification.getNotificationView().findViewById(
363                 R.id.card_view);
364 
365         if (cardView == null) return;
366 
367         if (panelExpanded) {
368             info.setTouchableInsets(
369                     ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
370             return;
371         }
372 
373         cardView.getLocationInWindow(mTmpTwoArray);
374         int minX = mTmpTwoArray[0];
375         int maxX = mTmpTwoArray[0] + cardView.getWidth();
376         int height = cardView.getHeight();
377         info.setTouchableInsets(
378                 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
379         info.touchableRegion.set(minX, mNotificationHeadsUpCardMarginTop, maxX,
380                 height + mNotificationHeadsUpCardMarginTop);
381     }
382 
playSound(AlertEntry alertEntry, NotificationListenerService.RankingMap rankingMap)383     private void playSound(AlertEntry alertEntry,
384             NotificationListenerService.RankingMap rankingMap) {
385         NotificationListenerService.Ranking ranking = getRanking();
386         if (rankingMap.getRanking(alertEntry.getKey(), ranking)) {
387             NotificationChannel notificationChannel = ranking.getChannel();
388             // If sound is not set on the notification channel and default is not chosen it
389             // can be null.
390             if (notificationChannel.getSound() != null) {
391                 // make the sound
392                 mBeeper.beep(alertEntry.getStatusBarNotification().getPackageName(),
393                         notificationChannel.getSound());
394             }
395         }
396     }
397 
shouldDismissOnSwipe(AlertEntry alertEntry)398     private boolean shouldDismissOnSwipe(AlertEntry alertEntry) {
399         return hasFullScreenIntent(alertEntry)
400                 && alertEntry.getNotification().category.equals(
401                 Notification.CATEGORY_CALL) && alertEntry.getStatusBarNotification().isOngoing();
402     }
403 
404     @VisibleForTesting
getActiveHeadsUpNotifications()405     protected Map<String, HeadsUpEntry> getActiveHeadsUpNotifications() {
406         return mActiveHeadsUpNotifications;
407     }
408 
setAutoDismissViews(HeadsUpEntry currentNotification, AlertEntry alertEntry)409     private void setAutoDismissViews(HeadsUpEntry currentNotification, AlertEntry alertEntry) {
410         // Should not auto dismiss if HUN has a full screen Intent.
411         if (hasFullScreenIntent(alertEntry)) {
412             return;
413         }
414         currentNotification.getHandler().removeCallbacksAndMessages(null);
415         currentNotification.getHandler().postDelayed(() -> dismissHUN(alertEntry), mDuration);
416     }
417 
418     /**
419      * Returns true if AlertEntry has a full screen Intent.
420      */
hasFullScreenIntent(AlertEntry alertEntry)421     private boolean hasFullScreenIntent(AlertEntry alertEntry) {
422         return alertEntry.getNotification().fullScreenIntent != null;
423     }
424 
425     /**
426      * Animates the heads up notification out of the screen and reset the views.
427      */
animateOutHUN(AlertEntry alertEntry, boolean isRemoved)428     private void animateOutHUN(AlertEntry alertEntry, boolean isRemoved) {
429         Log.d(TAG, "clearViews for Heads Up Notification: ");
430         // get the current notification to perform animations and remove it immediately from the
431         // active notification maps and cancel all other call backs if any.
432         HeadsUpEntry currentHeadsUpNotification = mActiveHeadsUpNotifications.get(
433                 alertEntry.getKey());
434         // view can also be removed when swipped away.
435         if (currentHeadsUpNotification == null) {
436             return;
437         }
438         currentHeadsUpNotification.getHandler().removeCallbacksAndMessages(null);
439         View view = currentHeadsUpNotification.getNotificationView();
440 
441         AnimatorSet animatorSet = mAnimationHelper.getAnimateOutAnimator(mContext, view);
442         animatorSet.setTarget(view);
443         animatorSet.addListener(new AnimatorListenerAdapter() {
444             @Override
445             public void onAnimationEnd(Animator animation) {
446                 mHunContainer.removeNotification(view);
447 
448                 // Remove HUN after the animation ends to prevent accidental touch on the card
449                 // triggering another remove call.
450                 mActiveHeadsUpNotifications.remove(alertEntry.getKey());
451 
452                 // If the HUN was not specifically removed then add it to the panel.
453                 if(!isRemoved) {
454                     handleHeadsUpNotificationStateChanged(alertEntry, /* isHeadsUp= */ false);
455                 }
456             }
457         });
458         animatorSet.start();
459     }
460 
dismissHUN(AlertEntry alertEntry)461     private void dismissHUN(AlertEntry alertEntry) {
462         animateOutHUN(alertEntry, /* isRemoved= */ false);
463     }
464 
removeHUN(AlertEntry alertEntry)465     private void removeHUN(AlertEntry alertEntry) {
466         animateOutHUN(alertEntry, /* isRemoved= */ true);
467     }
468 
469     /**
470      * Removes the view for the active heads up notification and also removes the HUN from the map
471      * of active Notifications.
472      */
resetView(AlertEntry alertEntry)473     private void resetView(AlertEntry alertEntry) {
474         HeadsUpEntry currentHeadsUpNotification = mActiveHeadsUpNotifications.get(
475                 alertEntry.getKey());
476         if (currentHeadsUpNotification == null) return;
477 
478         currentHeadsUpNotification.getHandler().removeCallbacksAndMessages(null);
479         mHunContainer.removeNotification(currentHeadsUpNotification.getNotificationView());
480         mActiveHeadsUpNotifications.remove(alertEntry.getKey());
481         handleHeadsUpNotificationStateChanged(alertEntry, /* isHeadsUp= */ false);
482     }
483 
484     /**
485      * Helper method that determines whether a notification should show as a heads-up.
486      *
487      * <p> A notification will never be shown as a heads-up if:
488      * <ul>
489      * <li> Keyguard (lock screen) is showing
490      * <li> OEMs configured CATEGORY_NAVIGATION should not be shown
491      * <li> Notification is muted.
492      * </ul>
493      *
494      * <p> A notification will be shown as a heads-up if:
495      * <ul>
496      * <li> Importance >= HIGH
497      * <li> it comes from an app signed with the platform key.
498      * <li> it comes from a privileged system app.
499      * <li> is a car compatible notification.
500      * {@link com.android.car.assist.client.CarAssistUtils#isCarCompatibleMessagingNotification}
501      * <li> Notification category is one of CATEGORY_CALL or CATEGORY_NAVIGATION
502      * </ul>
503      *
504      * <p> Group alert behavior still follows API documentation.
505      *
506      * @return true if a notification should be shown as a heads-up
507      */
shouldShowHeadsUp( AlertEntry alertEntry, NotificationListenerService.RankingMap rankingMap)508     private boolean shouldShowHeadsUp(
509             AlertEntry alertEntry,
510             NotificationListenerService.RankingMap rankingMap) {
511         if (mKeyguardManager.isKeyguardLocked()) {
512             return false;
513         }
514         Notification notification = alertEntry.getNotification();
515 
516         // Navigation notification configured by OEM
517         if (!mEnableNavigationHeadsup && Notification.CATEGORY_NAVIGATION.equals(
518                 notification.category)) {
519             return false;
520         }
521         // Group alert behavior
522         if (notification.suppressAlertingDueToGrouping()) {
523             return false;
524         }
525         // Messaging notification muted by user.
526         if (mNotificationDataManager.isMessageNotificationMuted(alertEntry)) {
527             return false;
528         }
529 
530         // Do not show if importance < HIGH
531         NotificationListenerService.Ranking ranking = getRanking();
532         if (rankingMap.getRanking(alertEntry.getKey(), ranking)) {
533             if (ranking.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
534                 return false;
535             }
536         }
537 
538         if (NotificationUtils.isSystemPrivilegedOrPlatformKey(mContext, alertEntry)) {
539             return true;
540         }
541 
542         // Allow car messaging type.
543         if (isCarCompatibleMessagingNotification(alertEntry.getStatusBarNotification())) {
544             return true;
545         }
546 
547         if (notification.category == null) {
548             Log.d(TAG, "category not set for: "
549                     + alertEntry.getStatusBarNotification().getPackageName());
550         }
551 
552         // Allow for Call, and nav TBT categories.
553         if (Notification.CATEGORY_CALL.equals(notification.category)
554                 || Notification.CATEGORY_NAVIGATION.equals(notification.category)) {
555             return true;
556         }
557         return false;
558     }
559 
560     @VisibleForTesting
getRanking()561     protected NotificationListenerService.Ranking getRanking() {
562         return new NotificationListenerService.Ranking();
563     }
564 
565     @Override
onUxRestrictionsChanged(CarUxRestrictions restrictions)566     public void onUxRestrictionsChanged(CarUxRestrictions restrictions) {
567         mShouldRestrictMessagePreview =
568                 (restrictions.getActiveRestrictions()
569                         & CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE) != 0;
570     }
571 
572     /**
573      * Sets the source of {@link View.OnClickListener}
574      *
575      * @param clickHandlerFactory used to generate onClickListeners
576      */
577     @VisibleForTesting
setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory)578     public void setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory) {
579         mClickHandlerFactory = clickHandlerFactory;
580     }
581 }
582