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