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;
}
}