Merge 855cb36ddd297b43f7788a2a1fad0b80271668eb on remote branch

Change-Id: Ifb0990042ab8f5bacf5fe2807075842d458e9bc4
diff --git a/OWNERS b/OWNERS
index d3a51e5..5fd89b8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,9 +1,9 @@
 # Default code reviewers picked from top 3 or more developers.
 # Please update this list if you find better candidates.
-ericberglund@google.com
-stenning@google.com
-priyanksingh@google.com
+abhijoy@google.com
 kwaky@google.com
-jonathankoo@google.com
+hseog@google.com
+priyanksingh@google.com
+stenning@google.com
 johnchoi@google.com
 juliacr@google.com
diff --git a/res/layout/basic_headsup_notification_template.xml b/res/layout/basic_headsup_notification_template.xml
index 10c775b..0f82a23 100644
--- a/res/layout/basic_headsup_notification_template.xml
+++ b/res/layout/basic_headsup_notification_template.xml
@@ -18,8 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/call_headsup_notification_template.xml b/res/layout/call_headsup_notification_template.xml
index 3a59f26..d15f13d 100644
--- a/res/layout/call_headsup_notification_template.xml
+++ b/res/layout/call_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/car_emergency_headsup_notification_template.xml b/res/layout/car_emergency_headsup_notification_template.xml
index 7e3b968..a2b71e1 100644
--- a/res/layout/car_emergency_headsup_notification_template.xml
+++ b/res/layout/car_emergency_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/car_information_headsup_notification_template.xml b/res/layout/car_information_headsup_notification_template.xml
index 09138f9..0411c51 100644
--- a/res/layout/car_information_headsup_notification_template.xml
+++ b/res/layout/car_information_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/car_warning_headsup_notification_template.xml b/res/layout/car_warning_headsup_notification_template.xml
index 09138f9..0411c51 100644
--- a/res/layout/car_warning_headsup_notification_template.xml
+++ b/res/layout/car_warning_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/headsup_container.xml b/res/layout/headsup_container.xml
index 9eec539..19d80d8 100644
--- a/res/layout/headsup_container.xml
+++ b/res/layout/headsup_container.xml
@@ -35,7 +35,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"/>
+        app:layout_constraintTop_toTopOf="parent"
+        app:shouldRestoreFocus="false"/>
 
     <View
         android:id="@+id/scrim"
diff --git a/res/layout/inbox_headsup_notification_template.xml b/res/layout/inbox_headsup_notification_template.xml
index 2d4e723..ae217d9 100644
--- a/res/layout/inbox_headsup_notification_template.xml
+++ b/res/layout/inbox_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/message_headsup_notification_template.xml b/res/layout/message_headsup_notification_template.xml
index ca5f19b..ea49d0e 100644
--- a/res/layout/message_headsup_notification_template.xml
+++ b/res/layout/message_headsup_notification_template.xml
@@ -19,8 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/layout/message_notification_template.xml b/res/layout/message_notification_template.xml
index 24ccd77..42b1f9a 100644
--- a/res/layout/message_notification_template.xml
+++ b/res/layout/message_notification_template.xml
@@ -54,11 +54,10 @@
                 style="@style/NotificationBodyTitleText"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_alignParentStart="true"
                 android:layout_below="@id/notification_header"
                 android:layout_marginEnd="@dimen/card_end_margin"
                 android:layout_marginStart="@dimen/card_start_margin"
-                android:layout_toStartOf="@id/notification_body_icon"/>
+                android:layout_toLeftOf="@id/notification_body_icon"/>
 
             <RelativeLayout
                 android:id="@+id/message_content"
@@ -66,7 +65,7 @@
                 android:layout_height="wrap_content"
                 android:layout_below="@id/notification_body_title"
                 android:layout_marginStart="@dimen/card_start_margin"
-                android:layout_toStartOf="@id/notification_body_icon">
+                android:layout_toLeftOf="@id/notification_body_icon">
 
                 <TextView
                     android:id="@+id/notification_body_content"
@@ -95,10 +94,9 @@
                 style="@style/NotificationBodyImageIcon"
                 android:layout_width="@dimen/notification_touch_target_size"
                 android:layout_height="@dimen/notification_touch_target_size"
-                android:layout_alignParentEnd="true"
-                android:layout_centerVertical="true"
-                android:layout_marginEnd="@dimen/card_end_margin"
-                android:layout_marginStart="@dimen/body_big_icon_margin"/>
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/notification_header"
+                android:layout_marginRight="@dimen/card_end_margin"/>
 
             <FrameLayout
                 android:layout_width="match_parent"
diff --git a/res/layout/message_notification_template_inner.xml b/res/layout/message_notification_template_inner.xml
index 0575509..eb3ffe6 100644
--- a/res/layout/message_notification_template_inner.xml
+++ b/res/layout/message_notification_template_inner.xml
@@ -33,13 +33,12 @@
 
     <RelativeLayout
         android:id="@+id/message_title_view"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_alignParentStart="true"
         android:layout_below="@+id/notification_header"
         android:layout_marginEnd="@dimen/card_end_margin"
         android:layout_marginStart="@dimen/card_start_margin"
-        android:layout_toStartOf="@id/notification_body_icon">
+        android:layout_toLeftOf="@id/notification_body_icon">
 
         <TextView
             android:id="@+id/notification_body_title"
@@ -65,7 +64,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/message_title_view"
         android:layout_marginStart="@dimen/card_start_margin"
-        android:layout_toStartOf="@id/notification_body_icon">
+        android:layout_toLeftOf="@id/notification_body_icon">
 
         <TextView
             android:id="@+id/notification_body_content"
@@ -93,10 +92,9 @@
         style="@style/NotificationBodyImageIcon"
         android:layout_width="@dimen/notification_touch_target_size"
         android:layout_height="@dimen/notification_touch_target_size"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true"
-        android:layout_marginBottom="@dimen/card_body_margin_bottom"
-        android:layout_marginEnd="@dimen/card_end_margin"/>
+        android:layout_alignParentRight="true"
+        android:layout_below="@+id/notification_header"
+        android:layout_marginRight="@dimen/card_end_margin"/>
 
     <FrameLayout
         android:layout_width="match_parent"
diff --git a/res/layout/navigation_headsup_notification_template.xml b/res/layout/navigation_headsup_notification_template.xml
index 4e4c432..b4b83e8 100644
--- a/res/layout/navigation_headsup_notification_template.xml
+++ b/res/layout/navigation_headsup_notification_template.xml
@@ -18,8 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:defaultFocus="@+id/action_1">
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
diff --git a/res/values/config.xml b/res/values/config.xml
index d2ec138..54f6ddd 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -38,4 +38,6 @@
     <!-- Animation helper for animating heads up notification showing on screen and leaving the screen. -->
     <string name="config_headsUpNotificationAnimationHelper" translatable="false">
         com.android.car.notification.headsup.animationhelper.CarHeadsUpNotificationTopAnimationHelper</string>
+    <!-- Whether to always show Notification's dismiss button even without the need to have rotary focus. -->
+    <bool name="config_alwaysShowNotificationDismissButton">false</bool>
 </resources>
diff --git a/src/com/android/car/notification/CarHeadsUpNotificationManager.java b/src/com/android/car/notification/CarHeadsUpNotificationManager.java
index d454087..29b238a 100644
--- a/src/com/android/car/notification/CarHeadsUpNotificationManager.java
+++ b/src/com/android/car/notification/CarHeadsUpNotificationManager.java
@@ -15,6 +15,11 @@
  */
 package com.android.car.notification;
 
+import static android.view.ViewTreeObserver.InternalInsetsInfo;
+import static android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import static android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
+import static android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
 import static com.android.car.assist.client.CarAssistUtils.isCarCompatibleMessagingNotification;
 
 import android.animation.Animator;
@@ -29,6 +34,7 @@
 import android.content.Context;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -79,7 +85,11 @@
 
     // key for the map is the statusbarnotification key
     private final Map<String, HeadsUpEntry> mActiveHeadsUpNotifications = new HashMap<>();
-    private final List<OnHeadsUpNotificationStateChange> mListeners = new ArrayList<>();
+    private final List<OnHeadsUpNotificationStateChange> mNotificationStateChangeListeners =
+            new ArrayList<>();
+    private final Map<HeadsUpEntry,
+            Pair<OnComputeInternalInsetsListener, OnGlobalFocusChangeListener>>
+            mRegisteredViewTreeListeners = new HashMap<>();
 
     private boolean mShouldRestrictMessagePreview;
     private NotificationClickHandlerFactory mClickHandlerFactory;
@@ -107,7 +117,7 @@
         mPreprocessingManager = PreprocessingManager.getInstance(context);
         mInflater = LayoutInflater.from(mContext);
         mClickHandlerFactory.registerClickListener(
-                (launchResult, alertEntry) -> dismissHUN(alertEntry));
+                (launchResult, alertEntry) -> dismissHun(alertEntry));
         mHunContainer = hunContainer;
     }
 
@@ -143,7 +153,7 @@
             if (CarNotificationDiff.sameNotificationKey(currentActiveHeadsUpNotification,
                     alertEntry)
                     && currentActiveHeadsUpNotification.getHandler().hasMessagesOrCallbacks()) {
-                dismissHUN(alertEntry);
+                dismissHun(alertEntry);
             }
             return false;
         }
@@ -171,14 +181,14 @@
                 System.currentTimeMillis() - currentActiveHeadsUpNotification.getPostTime();
         // ongoing notification that has passed the minimum threshold display time.
         if (totalDisplayDuration >= mMinDisplayDuration) {
-            removeHUN(alertEntry);
+            removeHun(alertEntry);
             return;
         }
 
         long earliestRemovalTime = mMinDisplayDuration - totalDisplayDuration;
 
         currentActiveHeadsUpNotification.getHandler().postDelayed(() ->
-                removeHUN(alertEntry), earliestRemovalTime);
+                removeHun(alertEntry), earliestRemovalTime);
     }
 
     /**
@@ -186,8 +196,8 @@
      */
     public void registerHeadsUpNotificationStateChangeListener(
             OnHeadsUpNotificationStateChange listener) {
-        if (!mListeners.contains(listener)) {
-            mListeners.add(listener);
+        if (!mNotificationStateChangeListeners.contains(listener)) {
+            mNotificationStateChangeListeners.add(listener);
         }
     }
 
@@ -196,7 +206,7 @@
      */
     public void unregisterHeadsUpNotificationStateChangeListener(
             OnHeadsUpNotificationStateChange listener) {
-        mListeners.remove(listener);
+        mNotificationStateChangeListeners.remove(listener);
     }
 
     /**
@@ -204,7 +214,7 @@
      * OnHeadsUpNotificationStateChange}s array.
      */
     private void handleHeadsUpNotificationStateChanged(AlertEntry alertEntry, boolean isHeadsUp) {
-        mListeners.forEach(
+        mNotificationStateChangeListeners.forEach(
                 listener -> listener.onStateChange(alertEntry, isHeadsUp));
     }
 
@@ -321,15 +331,20 @@
                     /* isHeadsUp= */ true);
         }
 
+        resetViewTreeListenersEntry(currentNotification);
+
+        ViewTreeObserver viewTreeObserver =
+                currentNotification.getNotificationView().getViewTreeObserver();
+
         // measure the size of the card and make that area of the screen touchable
-        currentNotification.getNotificationView().getViewTreeObserver()
-                .addOnComputeInternalInsetsListener(
-                        info -> setInternalInsetsInfo(info,
-                                currentNotification, /* panelExpanded= */false));
+        OnComputeInternalInsetsListener onComputeInternalInsetsListener =
+                info -> setInternalInsetsInfo(info, currentNotification,
+                        /* panelExpanded= */ false);
+        viewTreeObserver.addOnComputeInternalInsetsListener(onComputeInternalInsetsListener);
         // Get the height of the notification view after onLayout() in order to animate the
         // notification into the screen.
-        currentNotification.getNotificationView().getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
+        viewTreeObserver.addOnGlobalLayoutListener(
+                new OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
                         View view = currentNotification.getNotificationView();
@@ -343,6 +358,13 @@
                         view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                     }
                 });
+        // Reset the auto dismiss timeout for each rotary event.
+        OnGlobalFocusChangeListener onGlobalFocusChangeListener =
+                (oldFocus, newFocus) -> setAutoDismissViews(currentNotification, alertEntry);
+        viewTreeObserver.addOnGlobalFocusChangeListener(onGlobalFocusChangeListener);
+
+        mRegisteredViewTreeListeners.put(currentNotification,
+                new Pair<>(onComputeInternalInsetsListener, onGlobalFocusChangeListener));
 
         if (currentNotification.mIsNewHeadsUp) {
             // Add swipe gesture
@@ -354,12 +376,25 @@
             View dismissButton = currentNotification.getNotificationView().findViewById(
                     R.id.dismiss_button);
             if (dismissButton != null) {
-                dismissButton.setOnClickListener(v -> dismissHUN(alertEntry));
+                dismissButton.setOnClickListener(v -> dismissHun(alertEntry));
             }
         }
     }
 
-    protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
+    private void resetViewTreeListenersEntry(HeadsUpEntry headsUpEntry) {
+        Pair<OnComputeInternalInsetsListener, OnGlobalFocusChangeListener> listeners =
+                mRegisteredViewTreeListeners.get(headsUpEntry);
+        if (listeners == null) {
+            return;
+        }
+
+        ViewTreeObserver observer = headsUpEntry.getNotificationView().getViewTreeObserver();
+        observer.removeOnComputeInternalInsetsListener(listeners.first);
+        observer.removeOnGlobalFocusChangeListener(listeners.second);
+        mRegisteredViewTreeListeners.remove(headsUpEntry);
+    }
+
+    protected void setInternalInsetsInfo(InternalInsetsInfo info,
             HeadsUpEntry currentNotification, boolean panelExpanded) {
         // If the panel is not on screen don't modify the touch region
         if (!mHunContainer.isVisible()) return;
@@ -370,8 +405,7 @@
         if (cardView == null) return;
 
         if (panelExpanded) {
-            info.setTouchableInsets(
-                    ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+            info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
             return;
         }
 
@@ -379,8 +413,7 @@
         int minX = mTmpTwoArray[0];
         int maxX = mTmpTwoArray[0] + cardView.getWidth();
         int height = cardView.getHeight();
-        info.setTouchableInsets(
-                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
         info.touchableRegion.set(minX, mNotificationHeadsUpCardMarginTop, maxX,
                 height + mNotificationHeadsUpCardMarginTop);
     }
@@ -417,7 +450,7 @@
             return;
         }
         currentNotification.getHandler().removeCallbacksAndMessages(null);
-        currentNotification.getHandler().postDelayed(() -> dismissHUN(alertEntry), mDuration);
+        currentNotification.getHandler().postDelayed(() -> dismissHun(alertEntry), mDuration);
     }
 
     /**
@@ -430,7 +463,7 @@
     /**
      * Animates the heads up notification out of the screen and reset the views.
      */
-    private void animateOutHUN(AlertEntry alertEntry, boolean isRemoved) {
+    private void animateOutHun(AlertEntry alertEntry, boolean isRemoved) {
         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.
@@ -441,6 +474,7 @@
             return;
         }
         currentHeadsUpNotification.getHandler().removeCallbacksAndMessages(null);
+        resetViewTreeListenersEntry(currentHeadsUpNotification);
         View view = currentHeadsUpNotification.getNotificationView();
 
         AnimatorSet animatorSet = mAnimationHelper.getAnimateOutAnimator(mContext, view);
@@ -463,12 +497,12 @@
         animatorSet.start();
     }
 
-    private void dismissHUN(AlertEntry alertEntry) {
-        animateOutHUN(alertEntry, /* isRemoved= */ false);
+    private void dismissHun(AlertEntry alertEntry) {
+        animateOutHun(alertEntry, /* isRemoved= */ false);
     }
 
-    private void removeHUN(AlertEntry alertEntry) {
-        animateOutHUN(alertEntry, /* isRemoved= */ true);
+    private void removeHun(AlertEntry alertEntry) {
+        animateOutHun(alertEntry, /* isRemoved= */ true);
     }
 
     /**
@@ -484,6 +518,7 @@
         mHunContainer.removeNotification(currentHeadsUpNotification.getNotificationView());
         mActiveHeadsUpNotifications.remove(alertEntry.getKey());
         handleHeadsUpNotificationStateChanged(alertEntry, /* isHeadsUp= */ false);
+        resetViewTreeListenersEntry(currentHeadsUpNotification);
     }
 
     /**
diff --git a/src/com/android/car/notification/CarNotificationListener.java b/src/com/android/car/notification/CarNotificationListener.java
index 9d124e8..2aa5589 100644
--- a/src/com/android/car/notification/CarNotificationListener.java
+++ b/src/com/android/car/notification/CarNotificationListener.java
@@ -114,6 +114,11 @@
 
     @Override
     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (sbn == null) {
+            Log.e(TAG, "onNotificationPosted: StatusBarNotification is null");
+            return;
+        }
+
         Log.d(TAG, "onNotificationPosted: " + sbn);
         if (!isNotificationForCurrentUser(sbn)) {
             return;
@@ -125,8 +130,12 @@
 
     @Override
     public void onNotificationRemoved(StatusBarNotification sbn) {
-        Log.d(TAG, "onNotificationRemoved: " + sbn);
+        if (sbn == null) {
+            Log.e(TAG, "onNotificationRemoved: StatusBarNotification is null");
+            return;
+        }
 
+        Log.d(TAG, "onNotificationRemoved: " + sbn);
         AlertEntry alertEntry = mActiveNotifications.get(sbn.getKey());
 
         if (alertEntry != null) {
diff --git a/src/com/android/car/notification/CarNotificationView.java b/src/com/android/car/notification/CarNotificationView.java
index b55447b..ef3597d 100644
--- a/src/com/android/car/notification/CarNotificationView.java
+++ b/src/com/android/car/notification/CarNotificationView.java
@@ -11,6 +11,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.widget.Button;
 
@@ -50,6 +51,7 @@
     private boolean mIsClearAllActive = false;
     private List<NotificationGroup> mNotifications;
     private UxrContentLimiterImpl mUxrContentLimiter;
+    private KeyEventHandler mKeyEventHandler;
 
     public CarNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -110,6 +112,24 @@
         }
     }
 
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+
+        if (mKeyEventHandler != null) {
+            return mKeyEventHandler.dispatchKeyEvent(event);
+        }
+
+        return false;
+    }
+
+    /** Sets a {@link KeyEventHandler} to help interact with the notification panel. */
+    public void setKeyEventHandler(KeyEventHandler keyEventHandler) {
+        mKeyEventHandler = keyEventHandler;
+    }
+
     /**
      * Updates notifications and update views.
      */
@@ -362,4 +382,10 @@
 
         mAdapter.setNotificationsAsSeen(firstVisible, lastVisible);
     }
+
+    /** An interface to help interact with the notification panel. */
+    public interface KeyEventHandler {
+        /** Allows handling of a {@link KeyEvent} if it isn't already handled by the superclass. */
+        boolean dispatchKeyEvent(KeyEvent event);
+    }
 }
diff --git a/src/com/android/car/notification/NotificationClickHandlerFactory.java b/src/com/android/car/notification/NotificationClickHandlerFactory.java
index ac77867..be04bfb 100644
--- a/src/com/android/car/notification/NotificationClickHandlerFactory.java
+++ b/src/com/android/car/notification/NotificationClickHandlerFactory.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.service.notification.NotificationStats;
 import android.util.Log;
@@ -71,10 +73,12 @@
     private CarAssistUtils mCarAssistUtils;
     @Nullable
     private NotificationDataManager mNotificationDataManager;
+    private Handler mMainHandler;
 
     public NotificationClickHandlerFactory(IStatusBarService barService) {
         mBarService = barService;
         mCarAssistUtils = null;
+        mMainHandler = new Handler(Looper.getMainLooper());
     }
 
     /**
@@ -362,13 +366,8 @@
     }
 
     private void showToast(Context context, int resourceId) {
-        Toast toast = Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG);
-        // This flag is needed for the Toast to show up on the active user's screen since
-        // Notifications is part of SystemUI. SystemUI is owned by a system process, which runs in
-        // the background, so without this, the toast will never appear in the foreground.
-        toast.getWindowParams().privateFlags |=
-                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        toast.show();
+        mMainHandler.post(
+                Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG)::show);
     }
 
     private boolean shouldAutoCancel(AlertEntry alertEntry) {
diff --git a/src/com/android/car/notification/headsup/HeadsUpContainerView.java b/src/com/android/car/notification/headsup/HeadsUpContainerView.java
index 44fbec3..be9ebe2 100644
--- a/src/com/android/car/notification/headsup/HeadsUpContainerView.java
+++ b/src/com/android/car/notification/headsup/HeadsUpContainerView.java
@@ -103,11 +103,17 @@
             return false;
         }
 
-        View topMostChild = getChildAt(childCount - 1);
-        if (!(topMostChild instanceof FocusArea)) {
+        View topmostChild = getChildAt(childCount - 1);
+        if (!(topmostChild instanceof FocusArea)) {
             return false;
         }
 
-        return topMostChild.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+        FocusArea focusArea = (FocusArea) topmostChild;
+        View view = focusArea.findViewById(R.id.action_1);
+        if (view != null) {
+            focusArea.setDefaultFocus(view);
+        }
+
+        return topmostChild.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
     }
 }
diff --git a/src/com/android/car/notification/template/CarNotificationActionsView.java b/src/com/android/car/notification/template/CarNotificationActionsView.java
index f81cfbb..827e927 100644
--- a/src/com/android/car/notification/template/CarNotificationActionsView.java
+++ b/src/com/android/car/notification/template/CarNotificationActionsView.java
@@ -26,6 +26,7 @@
 import android.widget.Button;
 import android.widget.RelativeLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -45,7 +46,6 @@
 public class CarNotificationActionsView extends RelativeLayout implements
         PreprocessingManager.CallStateListener {
 
-    private static final String TAG = "CarNotificationAction";
     // Maximum 3 actions
     // https://developer.android.com/reference/android/app/Notification.Builder.html#addAction
     @VisibleForTesting
@@ -56,48 +56,52 @@
     static final int SECOND_MESSAGE_ACTION_BUTTON_INDEX = 1;
 
     private final List<Button> mActionButtons = new ArrayList<>();
+    private final Context mContext;
+    private final CarAssistUtils mCarAssistUtils;
 
     private boolean mIsCategoryCall;
     private boolean mIsInCall;
-    private Context mContext;
 
     public CarNotificationActionsView(Context context) {
-        super(context);
-        PreprocessingManager.getInstance(context).addCallStateListener(this::onCallStateChanged);
-        init(/* attrs= */ null);
+        this(context, /* attrs= */ null);
     }
 
     public CarNotificationActionsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        init(attrs);
+        this(context, attrs, /* defStyleAttr= */ 0);
     }
 
     public CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mContext = context;
-        init(attrs);
+        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
     }
 
-    public CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
+    public CarNotificationActionsView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        this(context, attrs, defStyleAttr, defStyleRes, new CarAssistUtils(context));
+    }
+
+    @VisibleForTesting
+    CarNotificationActionsView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes, @NonNull CarAssistUtils carAssistUtils) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
         mContext = context;
+        mCarAssistUtils = carAssistUtils;
         init(attrs);
     }
 
     private void init(@Nullable AttributeSet attrs) {
         if (attrs != null) {
             TypedArray attributes =
-                    getContext().obtainStyledAttributes(attrs, R.styleable.CarNotificationActionsView);
+                    mContext.obtainStyledAttributes(attrs, R.styleable.CarNotificationActionsView);
             mIsCategoryCall =
                     attributes.getBoolean(R.styleable.CarNotificationActionsView_categoryCall,
-                            /* default value= */false);
+                            /* defaultValue= */ false);
             attributes.recycle();
         }
-        PreprocessingManager.getInstance(getContext()).addCallStateListener(
-                this::onCallStateChanged);
-        inflate(getContext(), R.layout.car_notification_actions_view, /* root= */ this);
+
+        PreprocessingManager.getInstance(mContext).addCallStateListener(this);
+
+        inflate(mContext, R.layout.car_notification_actions_view, /* root= */ this);
     }
 
     /**
@@ -107,7 +111,6 @@
      * @param alertEntry          the notification that contains the actions.
      */
     public void bind(NotificationClickHandlerFactory clickHandlerFactory, AlertEntry alertEntry) {
-
         Notification notification = alertEntry.getNotification();
         Notification.Action[] actions = notification.actions;
         if (actions == null || actions.length == 0) {
@@ -116,7 +119,11 @@
 
         if (CarAssistUtils.isCarCompatibleMessagingNotification(
                 alertEntry.getStatusBarNotification())) {
-            createPlayButton(clickHandlerFactory, alertEntry);
+            boolean canPlayMessage = mCarAssistUtils.hasActiveAssistant()
+                    || mCarAssistUtils.isFallbackAssistantEnabled();
+            if (canPlayMessage) {
+                createPlayButton(clickHandlerFactory, alertEntry);
+            }
             createMuteButton(clickHandlerFactory, alertEntry);
             return;
         }
@@ -160,8 +167,7 @@
             button.setText(null);
             button.setOnClickListener(null);
         }
-        PreprocessingManager.getInstance(getContext()).removeCallStateListener(
-                this::onCallStateChanged);
+        PreprocessingManager.getInstance(getContext()).removeCallStateListener(this);
     }
 
     @Override
diff --git a/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java b/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
index a846c59..a0d04f9 100644
--- a/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
+++ b/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
@@ -92,6 +92,7 @@
     private boolean mEnableCardBackgroundColorForCategoryNavigation;
     private boolean mEnableCardBackgroundColorForSystemApp;
     private boolean mEnableSmallIconAccentColor;
+    private boolean mAlwaysShowDismissButton;
 
     /**
      * Tracks if the foreground colors have been calculated for the binding of the view holder.
@@ -110,8 +111,10 @@
         mBodyView = itemView.findViewById(R.id.notification_body);
         mActionsView = itemView.findViewById(R.id.notification_actions);
         mDismissButton = itemView.findViewById(R.id.dismiss_button);
+        mAlwaysShowDismissButton = mContext.getResources().getBoolean(
+                R.bool.config_alwaysShowNotificationDismissButton);
         mFocusChangeListener = (oldFocus, newFocus) -> {
-            if (mDismissButton != null) {
+            if (mDismissButton != null && !mAlwaysShowDismissButton) {
                 // The dismiss button should only be visible when the focus is on this notification
                 // or within it. Use alpha rather than visibility so that focus can move up to the
                 // previous notification's dismiss button.
@@ -296,7 +299,9 @@
 
         itemView.getViewTreeObserver().removeOnGlobalFocusChangeListener(mFocusChangeListener);
         if (mDismissButton != null) {
-            mDismissButton.setImageAlpha(0);
+            if (!mAlwaysShowDismissButton) {
+                mDismissButton.setImageAlpha(0);
+            }
             mDismissButton.setVisibility(View.GONE);
         }
     }
@@ -330,7 +335,9 @@
             hideDismissButton();
             return;
         }
-        mDismissButton.setImageAlpha(0);
+        if (!mAlwaysShowDismissButton) {
+            mDismissButton.setImageAlpha(0);
+        }
         mDismissButton.setVisibility(View.VISIBLE);
         if (!isHeadsUp) {
             // Only set the click listener here for panel notifications - HUNs already have one
diff --git a/tests/robotests/src/com/android/car/notification/template/CarNotificationActionsViewTest.java b/tests/robotests/src/com/android/car/notification/template/CarNotificationActionsViewTest.java
index a658c00..e7e1464 100644
--- a/tests/robotests/src/com/android/car/notification/template/CarNotificationActionsViewTest.java
+++ b/tests/robotests/src/com/android/car/notification/template/CarNotificationActionsViewTest.java
@@ -165,6 +165,42 @@
     }
 
     @Test
+    public void onBind_carCompatibleMessage_noAssistantNoFallback_playButtonIsHidden() {
+        ShadowCarAssistUtils.setHasActiveAssistant(false);
+
+        finishInflateWithIsCall(/* isCall= */ false);
+        statusBarNotificationHasActions(/* hasActions= */ true);
+        notificationIsCarCompatibleMessage(/* isCarCompatibleMessage= */ true);
+
+        AlertEntry alertEntry = new AlertEntry(mStatusBarNotification);
+        mCarNotificationActionsView.bind(mNotificationClickHandlerFactory, alertEntry);
+        Button playButton = mCarNotificationActionsView.getActionButtons().get(
+                CarNotificationActionsView.FIRST_MESSAGE_ACTION_BUTTON_INDEX);
+
+        assertThat(playButton.getVisibility()).isNotEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onBind_carCompatibleMessage_noAssistantWithFallback_playButtonIsVisible() {
+        ShadowCarAssistUtils.setHasActiveAssistant(false);
+        ShadowCarAssistUtils.setIsFallbackAssistantEnabled(true);
+
+        finishInflateWithIsCall(/* isCall= */ false);
+        statusBarNotificationHasActions(/* hasActions= */ true);
+        notificationIsCarCompatibleMessage(/* isCarCompatibleMessage= */ true);
+
+        AlertEntry alertEntry = new AlertEntry(mStatusBarNotification);
+        mCarNotificationActionsView.bind(mNotificationClickHandlerFactory, alertEntry);
+        Button playButton = mCarNotificationActionsView.getActionButtons().get(
+                CarNotificationActionsView.FIRST_MESSAGE_ACTION_BUTTON_INDEX);
+
+        assertThat(playButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(playButton.getText())
+                .isEqualTo(mContext.getString(R.string.assist_action_play_label));
+        assertThat(playButton.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
     public void onBind_actionExists_isCarCompatibleMessage_muteButtonIsVisible() {
         finishInflateWithIsCall(/* isCall= */ false);
         statusBarNotificationHasActions(/* hasActions= */ true);
diff --git a/tests/robotests/src/com/android/car/notification/testutils/ShadowCarAssistUtils.java b/tests/robotests/src/com/android/car/notification/testutils/ShadowCarAssistUtils.java
index 0429a1b..18f716c 100644
--- a/tests/robotests/src/com/android/car/notification/testutils/ShadowCarAssistUtils.java
+++ b/tests/robotests/src/com/android/car/notification/testutils/ShadowCarAssistUtils.java
@@ -32,6 +32,9 @@
     private static List<String> sMessageNotificationSbnKeys = new ArrayList<>();
     private static int sRequestAssistantVoiceActionCount = 0;
 
+    private static boolean mHasActiveAssistant = true;
+    private static boolean mIsFallbackAssistantEnabled = false;
+
     @Implementation
     protected static boolean isCarCompatibleMessagingNotification(StatusBarNotification sbn) {
         return sMessageNotificationSbnKeys.contains(sbn.getKey());
@@ -43,6 +46,16 @@
         sRequestAssistantVoiceActionCount++;
     }
 
+    @Implementation
+    public boolean hasActiveAssistant() {
+        return mHasActiveAssistant;
+    }
+
+    @Implementation
+    public boolean isFallbackAssistantEnabled() {
+        return mIsFallbackAssistantEnabled;
+    }
+
     public static void addMessageNotification(String messageSbnKey) {
         sMessageNotificationSbnKeys.add(messageSbnKey);
     }
@@ -51,6 +64,14 @@
         return sRequestAssistantVoiceActionCount;
     }
 
+    public static void setHasActiveAssistant(boolean hasActiveAssistant) {
+        mHasActiveAssistant = hasActiveAssistant;
+    }
+
+    public static void setIsFallbackAssistantEnabled(boolean isFallbackAssistantEnabled) {
+        mIsFallbackAssistantEnabled = isFallbackAssistantEnabled;
+    }
+
     /**
      * Resets the shadow state.
      */
@@ -58,5 +79,8 @@
     public static void reset() {
         sMessageNotificationSbnKeys.clear();
         sRequestAssistantVoiceActionCount = 0;
+
+        mHasActiveAssistant = true;
+        mIsFallbackAssistantEnabled = false;
     }
 }