blob: b26f432a544cb2722ac5be9239bcb17df817f340 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.notification;
import static com.android.car.assist.client.CarAssistUtils.isCarCompatibleMessagingNotification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import com.android.car.notification.template.BasicNotificationViewHolder;
import com.android.car.notification.template.CallNotificationViewHolder;
import com.android.car.notification.template.EmergencyNotificationViewHolder;
import com.android.car.notification.template.InboxNotificationViewHolder;
import com.android.car.notification.template.MessageNotificationViewHolder;
import com.android.car.notification.template.NavigationNotificationViewHolder;
import java.util.HashMap;
import java.util.Map;
/**
* Notification Manager for heads-up notifications in car.
*/
public class CarHeadsUpNotificationManager
implements CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
private static final String TAG = CarHeadsUpNotificationManager.class.getSimpleName();
private final Beeper mBeeper;
private final Context mContext;
private final boolean mEnableNavigationHeadsup;
private final long mDuration;
private final long mMinDisplayDuration;
private final long mEnterAnimationDuration;
private final long mAlphaEnterAnimationDuration;
private final long mExitAnimationDuration;
private final int mNotificationHeadsUpCardMarginTop;
private final KeyguardManager mKeyguardManager;
private final CarUserManagerHelper mCarUserManagerHelper;
private final PreprocessingManager mPreprocessingManager;
private final WindowManager mWindowManager;
private final LayoutInflater mInflater;
private boolean mShouldRestrictMessagePreview;
private NotificationClickHandlerFactory mClickHandlerFactory;
private NotificationDataManager mNotificationDataManager;
// key for the map is the statusbarnotification key
private final Map<String, HeadsUpEntry> mActiveHeadsUpNotifications;
// view that contains scrim and notification content
protected final View mHeadsUpPanel;
// framelayout that notification content should be added to.
protected final FrameLayout mHeadsUpContentFrame;
public CarHeadsUpNotificationManager(Context context,
NotificationClickHandlerFactory clickHandlerFactory,
NotificationDataManager notificationDataManager) {
mContext = context.getApplicationContext();
mEnableNavigationHeadsup =
context.getResources().getBoolean(R.bool.config_showNavigationHeadsup);
mClickHandlerFactory = clickHandlerFactory;
mNotificationDataManager = notificationDataManager;
mBeeper = new Beeper(mContext);
mDuration = mContext.getResources().getInteger(R.integer.headsup_notification_duration_ms);
mNotificationHeadsUpCardMarginTop = (int) mContext.getResources().getDimension(
R.dimen.headsup_notification_top_margin);
mMinDisplayDuration = mContext.getResources().getInteger(
R.integer.heads_up_notification_minimum_time);
mEnterAnimationDuration =
mContext.getResources().getInteger(R.integer.headsup_total_enter_duration_ms);
mAlphaEnterAnimationDuration =
mContext.getResources().getInteger(R.integer.headsup_alpha_enter_duration_ms);
mExitAnimationDuration =
mContext.getResources().getInteger(R.integer.headsup_exit_duration_ms);
mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
mPreprocessingManager = PreprocessingManager.getInstance(context);
mWindowManager =
(WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mInflater = LayoutInflater.from(mContext);
mActiveHeadsUpNotifications = new HashMap<>();
mHeadsUpPanel = createHeadsUpPanel();
mHeadsUpContentFrame = mHeadsUpPanel.findViewById(R.id.headsup_content);
mCarUserManagerHelper = new CarUserManagerHelper(mContext);
addHeadsUpPanelToDisplay();
}
/**
* Construct and return the heads up panel.
*
* @return view that contains R.id.headsup_content
*/
protected View createHeadsUpPanel() {
return mInflater.inflate(R.layout.headsup_container, null);
}
/**
* Attach the heads up panel to the display
*/
protected void addHeadsUpPanelToDisplay() {
WindowManager.LayoutParams wrapperParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
// This type allows covering status bar and receiving touch input
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
wrapperParams.gravity = Gravity.TOP;
mHeadsUpPanel.setVisibility(View.INVISIBLE);
mWindowManager.addView(mHeadsUpPanel, wrapperParams);
}
/**
* Set the Heads Up view to visible
*/
protected void setHeadsUpVisible() {
mHeadsUpPanel.setVisibility(View.VISIBLE);
}
/**
* Show the notification as a heads-up if it meets the criteria.
*/
public void maybeShowHeadsUp(
StatusBarNotification statusBarNotification,
NotificationListenerService.RankingMap rankingMap,
Map<String, StatusBarNotification> activeNotifications) {
if (!shouldShowHeadsUp(statusBarNotification, rankingMap)) {
// check if this is a update to the existing notification and if it should still show
// as a heads up or not.
HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
if (currentActiveHeadsUpNotification == null) {
activeNotifications.put(statusBarNotification.getKey(), statusBarNotification);
return;
}
if (CarNotificationDiff.sameNotificationKey(
currentActiveHeadsUpNotification.getStatusBarNotification(),
statusBarNotification)
&& currentActiveHeadsUpNotification.getHandler().hasMessagesOrCallbacks()) {
animateOutHUN(statusBarNotification);
}
activeNotifications.put(statusBarNotification.getKey(), statusBarNotification);
return;
}
if (!activeNotifications.containsKey(statusBarNotification.getKey()) || canUpdate(
statusBarNotification) || alertAgain(statusBarNotification.getNotification())) {
showHeadsUp(mPreprocessingManager.optimizeForDriving(statusBarNotification),
rankingMap);
}
activeNotifications.put(statusBarNotification.getKey(), statusBarNotification);
}
/**
* This method gets called when an app wants to cancel or withdraw its notification.
*/
public void maybeRemoveHeadsUp(StatusBarNotification statusBarNotification) {
HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
// if the heads up notification is already removed do nothing.
if (currentActiveHeadsUpNotification == null) {
return;
}
long totalDisplayDuration =
System.currentTimeMillis() - currentActiveHeadsUpNotification.getPostTime();
// ongoing notification that has passed the minimum threshold display time.
if (totalDisplayDuration >= mMinDisplayDuration) {
animateOutHUN(statusBarNotification);
return;
}
long earliestRemovalTime = mMinDisplayDuration - totalDisplayDuration;
currentActiveHeadsUpNotification.getHandler().postDelayed(() ->
animateOutHUN(statusBarNotification), earliestRemovalTime);
}
/**
* Returns true if the notification's flag is not set to
* {@link Notification#FLAG_ONLY_ALERT_ONCE}
*/
private boolean alertAgain(Notification newNotification) {
return (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
}
/**
* Return true if the currently displaying notification have the same key as the new added
* notification. In that case it will be considered as an update to the currently displayed
* notification.
*/
private boolean isUpdate(StatusBarNotification statusBarNotification) {
HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
if (currentActiveHeadsUpNotification == null) {
return false;
}
return CarNotificationDiff.sameNotificationKey(
currentActiveHeadsUpNotification.getStatusBarNotification(),
statusBarNotification);
}
/**
* Updates only when the notification is being displayed.
*/
private boolean canUpdate(StatusBarNotification statusBarNotification) {
HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
return currentActiveHeadsUpNotification != null && System.currentTimeMillis() -
currentActiveHeadsUpNotification.getPostTime() < mDuration;
}
/**
* Returns the active headsUpEntry or creates a new one while adding it to the list of
* mActiveHeadsUpNotifications.
*/
private HeadsUpEntry addNewHeadsUpEntry(StatusBarNotification statusBarNotification) {
HeadsUpEntry currentActiveHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
if (currentActiveHeadsUpNotification == null) {
currentActiveHeadsUpNotification = new HeadsUpEntry(statusBarNotification);
mActiveHeadsUpNotifications.put(statusBarNotification.getKey(),
currentActiveHeadsUpNotification);
currentActiveHeadsUpNotification.isAlertAgain = alertAgain(
statusBarNotification.getNotification());
currentActiveHeadsUpNotification.isNewHeadsUp = true;
return currentActiveHeadsUpNotification;
}
currentActiveHeadsUpNotification.isNewHeadsUp = false;
currentActiveHeadsUpNotification.isAlertAgain = alertAgain(
statusBarNotification.getNotification());
if (currentActiveHeadsUpNotification.isAlertAgain) {
// This is a ongoing notification which needs to be alerted again to the user. This
// requires for the post time to be updated.
currentActiveHeadsUpNotification.updatePostTime();
}
return currentActiveHeadsUpNotification;
}
/**
* Controls three major conditions while showing heads up notification.
* <p>
* <ol>
* <li> When a new HUN comes in it will be displayed with animations
* <li> If an update to existing HUN comes in which enforces to alert the HUN again to user,
* then the post time will be updated to current time. This will only be done if {@link
* Notification#FLAG_ONLY_ALERT_ONCE} flag is not set.
* <li> If an update to existing HUN comes in which just updates the data and does not want to
* alert itself again, then the animations will not be shown and the data will get updated. This
* will only be done if {@link Notification#FLAG_ONLY_ALERT_ONCE} flag is not set.
* </ol>
*/
private void showHeadsUp(StatusBarNotification statusBarNotification,
NotificationListenerService.RankingMap rankingMap) {
// Show animations only when there is no active HUN and notification is new. This check
// needs to be done here because after this the new notification will be added to the map
// holding ongoing notifications.
boolean shouldShowAnimation = !isUpdate(statusBarNotification);
HeadsUpEntry currentNotification = addNewHeadsUpEntry(statusBarNotification);
if (currentNotification.isNewHeadsUp) {
playSound(statusBarNotification, rankingMap);
setHeadsUpVisible();
setAutoDismissViews(currentNotification, statusBarNotification);
} else if (currentNotification.isAlertAgain) {
setAutoDismissViews(currentNotification, statusBarNotification);
}
@NotificationViewType int viewType = getNotificationViewType(statusBarNotification);
mClickHandlerFactory.setHeadsUpNotificationCallBack(
() -> animateOutHUN(statusBarNotification));
currentNotification.setClickHandlerFactory(mClickHandlerFactory);
switch (viewType) {
case NotificationViewType.CAR_EMERGENCY_HEADSUP: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.car_emergency_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new EmergencyNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.NAVIGATION: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.navigation_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new NavigationNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.CALL: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.call_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new CallNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.CAR_WARNING_HEADSUP: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.car_warning_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
// Using the basic view holder because they share the same view binding logic
// OEMs should create view holders if needed
currentNotification.setViewHolder(
new BasicNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification, /* isInGroup= */
false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.CAR_INFORMATION_HEADSUP: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.car_information_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
// Using the basic view holder because they share the same view binding logic
// OEMs should create view holders if needed
currentNotification.setViewHolder(
new BasicNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.MESSAGE_HEADSUP: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.message_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new MessageNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
if (mShouldRestrictMessagePreview) {
((MessageNotificationViewHolder) currentNotification.getViewHolder())
.bindRestricted(statusBarNotification, /* isInGroup= */
false, /* isHeadsUp= */ true);
} else {
currentNotification.getViewHolder().bind(statusBarNotification, /* isInGroup= */
false, /* isHeadsUp= */ true);
}
break;
}
case NotificationViewType.INBOX_HEADSUP: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.inbox_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new InboxNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
case NotificationViewType.BASIC_HEADSUP:
default: {
if (currentNotification.getNotificationView() == null) {
currentNotification.setNotificationView(mInflater.inflate(
R.layout.basic_headsup_notification_template,
null));
mHeadsUpContentFrame.addView(currentNotification.getNotificationView());
currentNotification.setViewHolder(
new BasicNotificationViewHolder(
currentNotification.getNotificationView(),
mClickHandlerFactory));
}
currentNotification.getViewHolder().bind(statusBarNotification,
/* isInGroup= */ false, /* isHeadsUp= */ true);
break;
}
}
// measure the size of the card and make that area of the screen touchable
currentNotification.getNotificationView().getViewTreeObserver()
.addOnComputeInternalInsetsListener(
info -> setInternalInsetsInfo(info,
currentNotification, /* panelExpanded= */false));
// Get the height of the notification view after onLayout()
// in order animate the notification in
currentNotification.getNotificationView().getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int notificationHeight =
currentNotification.getNotificationView().getHeight();
if (shouldShowAnimation) {
currentNotification.getNotificationView().setY(0 - notificationHeight);
currentNotification.getNotificationView().setAlpha(0f);
Interpolator yPositionInterpolator = AnimationUtils.loadInterpolator(
mContext,
R.interpolator.heads_up_entry_direction_interpolator);
Interpolator alphaInterpolator = AnimationUtils.loadInterpolator(
mContext,
R.interpolator.heads_up_entry_alpha_interpolator);
ObjectAnimator moveY = ObjectAnimator.ofFloat(
currentNotification.getNotificationView(), "y", 0f);
moveY.setDuration(mEnterAnimationDuration);
moveY.setInterpolator(yPositionInterpolator);
ObjectAnimator alpha = ObjectAnimator.ofFloat(
currentNotification.getNotificationView(), "alpha", 1f);
alpha.setDuration(mAlphaEnterAnimationDuration);
alpha.setInterpolator(alphaInterpolator);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(moveY, alpha);
animatorSet.start();
}
currentNotification.getNotificationView().getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
if (currentNotification.isNewHeadsUp) {
boolean shouldDismissOnSwipe = true;
if (shouldDismissOnSwipe(statusBarNotification)) {
shouldDismissOnSwipe = false;
}
// Add swipe gesture
View cardView = currentNotification.getNotificationView().findViewById(R.id.card_view);
cardView.setOnTouchListener(
new HeadsUpNotificationOnTouchListener(cardView, shouldDismissOnSwipe,
() -> resetView(statusBarNotification)));
}
}
protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
HeadsUpEntry currentNotification, boolean panelExpanded) {
// If the panel is not on screen don't modify the touch region
if (mHeadsUpPanel.getVisibility() != View.VISIBLE) return;
int[] mTmpTwoArray = new int[2];
View cardView = currentNotification.getNotificationView().findViewById(
R.id.card_view);
if (cardView == null) return;
if (panelExpanded) {
info.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
cardView.getLocationOnScreen(mTmpTwoArray);
int minX = mTmpTwoArray[0];
int maxX = mTmpTwoArray[0] + cardView.getWidth();
int height = cardView.getHeight();
info.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
info.touchableRegion.set(minX, mNotificationHeadsUpCardMarginTop, maxX,
height + mNotificationHeadsUpCardMarginTop);
}
private void playSound(StatusBarNotification statusBarNotification,
NotificationListenerService.RankingMap rankingMap) {
NotificationListenerService.Ranking ranking = getRanking();
if (rankingMap.getRanking(statusBarNotification.getKey(), ranking)) {
NotificationChannel notificationChannel = ranking.getChannel();
// If sound is not set on the notification channel and default is not chosen it
// can be null.
if (notificationChannel.getSound() != null) {
// make the sound
mBeeper.beep(statusBarNotification.getPackageName(),
notificationChannel.getSound());
}
}
}
private boolean shouldDismissOnSwipe(StatusBarNotification statusBarNotification) {
return hasFullScreenIntent(statusBarNotification)
&& statusBarNotification.getNotification().category.equals(
Notification.CATEGORY_CALL) && statusBarNotification.isOngoing();
}
@VisibleForTesting
protected Map<String, HeadsUpEntry> getActiveHeadsUpNotifications() {
return mActiveHeadsUpNotifications;
}
private void setAutoDismissViews(HeadsUpEntry currentNotification,
StatusBarNotification statusBarNotification) {
// Should not auto dismiss if HUN has a full screen Intent.
if (hasFullScreenIntent(statusBarNotification)) {
return;
}
currentNotification.getHandler().removeCallbacksAndMessages(null);
currentNotification.getHandler().postDelayed(() -> animateOutHUN(statusBarNotification),
mDuration);
}
/**
* Returns true if StatusBarNotification has a full screen Intent.
*/
private boolean hasFullScreenIntent(StatusBarNotification sbn) {
return sbn.getNotification().fullScreenIntent != null;
}
/**
* Animates the heads up notification out of the screen and reset the views.
*/
private void animateOutHUN(StatusBarNotification statusBarNotification) {
Log.d(TAG, "clearViews for Heads Up Notification: ");
// get the current notification to perform animations and remove it immediately from the
// active notification maps and cancel all other call backs if any.
HeadsUpEntry currentHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
// view can also be removed when swipped away.
if (currentHeadsUpNotification == null) {
return;
}
currentHeadsUpNotification.getHandler().removeCallbacksAndMessages(null);
currentHeadsUpNotification.getClickHandlerFactory().setHeadsUpNotificationCallBack(null);
Interpolator exitInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.heads_up_exit_direction_interpolator);
Interpolator alphaInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.heads_up_exit_alpha_interpolator);
ObjectAnimator moveY = ObjectAnimator.ofFloat(
currentHeadsUpNotification.getNotificationView(), "y",
-1 * currentHeadsUpNotification.getNotificationView().getHeight());
moveY.setDuration(mExitAnimationDuration);
moveY.setInterpolator(exitInterpolator);
ObjectAnimator alpha = ObjectAnimator.ofFloat(
currentHeadsUpNotification.getNotificationView(), "alpha", 1f);
alpha.setDuration(mExitAnimationDuration);
alpha.setInterpolator(alphaInterpolator);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(moveY, alpha);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
removeNotificationFromPanel(currentHeadsUpNotification);
// Remove HUN after the animation ends to prevent accidental touch on the card
// triggering another remove call.
mActiveHeadsUpNotifications.remove(statusBarNotification.getKey());
}
});
animatorSet.start();
}
/**
* Remove notification from the screen. If it was the last notification hide the heads up panel.
*
* @param currentHeadsUpNotification The notification to remove
*/
protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) {
mHeadsUpContentFrame.removeView(currentHeadsUpNotification.getNotificationView());
if (mHeadsUpContentFrame.getChildCount() == 0) {
mHeadsUpPanel.setVisibility(View.INVISIBLE);
}
}
/**
* Removes the view for the active heads up notification and also removes the HUN from the map
* of active Notifications.
*/
private void resetView(StatusBarNotification statusBarNotification) {
HeadsUpEntry currentHeadsUpNotification = mActiveHeadsUpNotifications.get(
statusBarNotification.getKey());
if (currentHeadsUpNotification == null) return;
currentHeadsUpNotification.getClickHandlerFactory().setHeadsUpNotificationCallBack(null);
currentHeadsUpNotification.getHandler().removeCallbacksAndMessages(null);
removeNotificationFromPanel(currentHeadsUpNotification);
mActiveHeadsUpNotifications.remove(statusBarNotification.getKey());
}
/**
* Choose a correct notification layout for this heads-up notification.
* Note that the layout chosen can be different for the same notification
* in the notification center.
*/
@NotificationViewType
private static int getNotificationViewType(StatusBarNotification statusBarNotification) {
String category = statusBarNotification.getNotification().category;
if (category != null) {
switch (category) {
case Notification.CATEGORY_CAR_EMERGENCY:
return NotificationViewType.CAR_EMERGENCY_HEADSUP;
case Notification.CATEGORY_NAVIGATION:
return NotificationViewType.NAVIGATION;
case Notification.CATEGORY_CALL:
return NotificationViewType.CALL;
case Notification.CATEGORY_CAR_WARNING:
return NotificationViewType.CAR_WARNING_HEADSUP;
case Notification.CATEGORY_CAR_INFORMATION:
return NotificationViewType.CAR_INFORMATION_HEADSUP;
case Notification.CATEGORY_MESSAGE:
return NotificationViewType.MESSAGE_HEADSUP;
default:
break;
}
}
Bundle extras = statusBarNotification.getNotification().extras;
if (extras.containsKey(Notification.EXTRA_BIG_TEXT)
&& extras.containsKey(Notification.EXTRA_SUMMARY_TEXT)) {
return NotificationViewType.INBOX_HEADSUP;
}
// progress, media, big text, big picture, and basic templates
return NotificationViewType.BASIC_HEADSUP;
}
/**
* Helper method that determines whether a notification should show as a heads-up.
*
* <p> A notification will never be shown as a heads-up if:
* <ul>
* <li> Keyguard (lock screen) is showing
* <li> OEMs configured CATEGORY_NAVIGATION should not be shown
* <li> Notification is muted.
* </ul>
*
* <p> A notification will be shown as a heads-up if:
* <ul>
* <li> Importance >= HIGH
* <li> it comes from an app signed with the platform key.
* <li> it comes from a privileged system app.
* <li> is a car compatible notification.
* {@link com.android.car.assist.client.CarAssistUtils#isCarCompatibleMessagingNotification}
* <li> Notification category is one of CATEGORY_CALL or CATEGORY_NAVIGATION
* </ul>
*
* <p> Group alert behavior still follows API documentation.
*
* @return true if a notification should be shown as a heads-up
*/
private boolean shouldShowHeadsUp(
StatusBarNotification statusBarNotification,
NotificationListenerService.RankingMap rankingMap) {
if (mKeyguardManager.isKeyguardLocked()) {
return false;
}
Notification notification = statusBarNotification.getNotification();
// Navigation notification configured by OEM
if (!mEnableNavigationHeadsup && Notification.CATEGORY_NAVIGATION.equals(
notification.category)) {
return false;
}
// Group alert behavior
if (notification.suppressAlertingDueToGrouping()) {
return false;
}
// Messaging notification muted by user.
if (mNotificationDataManager.isMessageNotificationMuted(statusBarNotification)) {
return false;
}
// Do not show if importance < HIGH
NotificationListenerService.Ranking ranking = getRanking();
if (rankingMap.getRanking(statusBarNotification.getKey(), ranking)) {
if (ranking.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
return false;
}
}
if (NotificationUtils.isSystemPrivilegedOrPlatformKey(mContext,
statusBarNotification)) {
return true;
}
// Allow car messaging type.
if (isCarCompatibleMessagingNotification(statusBarNotification)) {
return true;
}
if (notification.category == null) {
Log.d(TAG, "category not set for: " + statusBarNotification.getPackageName());
}
// Allow for Call, and nav TBT categories.
if (Notification.CATEGORY_CALL.equals(notification.category)
|| Notification.CATEGORY_NAVIGATION.equals(notification.category)) {
return true;
}
return false;
}
@VisibleForTesting
protected NotificationListenerService.Ranking getRanking() {
return new NotificationListenerService.Ranking();
}
@Override
public void onUxRestrictionsChanged(CarUxRestrictions restrictions) {
mShouldRestrictMessagePreview =
(restrictions.getActiveRestrictions()
& CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE) != 0;
}
/**
* Sets the source of {@link View.OnClickListener}
*
* @param clickHandlerFactory used to generate onClickListeners
*/
@VisibleForTesting
public void setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory) {
mClickHandlerFactory = clickHandlerFactory;
}
/**
* Callback that will be issued after a heads up notification is clicked
*/
public interface Callback {
/**
* Clears Heads up notification on click.
*/
void clearHeadsUpNotification();
}
}