1 package com.android.car.notification; 2 3 import android.car.drivingstate.CarUxRestrictions; 4 import android.car.drivingstate.CarUxRestrictionsManager; 5 import android.content.Context; 6 import android.graphics.Rect; 7 import android.util.AttributeSet; 8 import android.view.View; 9 import android.widget.Button; 10 11 import androidx.annotation.NonNull; 12 import androidx.constraintlayout.widget.ConstraintLayout; 13 import androidx.recyclerview.widget.LinearLayoutManager; 14 import androidx.recyclerview.widget.RecyclerView; 15 import androidx.recyclerview.widget.RecyclerView.OnScrollListener; 16 import androidx.recyclerview.widget.SimpleItemAnimator; 17 18 import java.util.List; 19 20 21 /** 22 * Layout that contains Car Notifications. 23 * 24 * It does some extra setup in the onFinishInflate method because it may not get used from an 25 * activity where one would normally attach RecyclerViews 26 */ 27 public class CarNotificationView extends ConstraintLayout 28 implements CarUxRestrictionsManager.OnUxRestrictionsChangedListener { 29 30 private CarNotificationViewAdapter mAdapter; 31 private Context mContext; 32 private LinearLayoutManager mLayoutManager; 33 private NotificationDataManager mNotificationDataManager; 34 CarNotificationView(Context context, AttributeSet attrs)35 public CarNotificationView(Context context, AttributeSet attrs) { 36 super(context, attrs); 37 mContext = context; 38 } 39 40 /** 41 * Attaches the CarNotificationViewAdapter and CarNotificationItemTouchListener to the 42 * notification list. 43 */ 44 @Override onFinishInflate()45 protected void onFinishInflate() { 46 super.onFinishInflate(); 47 RecyclerView listView = findViewById(R.id.notifications); 48 49 listView.setClipChildren(false); 50 mLayoutManager = new LinearLayoutManager(mContext); 51 listView.setLayoutManager(mLayoutManager); 52 listView.addItemDecoration(new TopAndBottomOffsetDecoration( 53 mContext.getResources().getDimensionPixelSize(R.dimen.item_spacing))); 54 listView.addItemDecoration(new ItemSpacingDecoration( 55 mContext.getResources().getDimensionPixelSize(R.dimen.item_spacing))); 56 57 mAdapter = new CarNotificationViewAdapter(mContext, /* isGroupNotificationAdapter= */ 58 false); 59 listView.setAdapter(mAdapter); 60 61 ((SimpleItemAnimator) listView.getItemAnimator()).setSupportsChangeAnimations(false); 62 listView.addOnItemTouchListener(new CarNotificationItemTouchListener(mContext, mAdapter)); 63 64 Button clearAllButton = findViewById(R.id.clear_all_button); 65 if (clearAllButton != null) { 66 clearAllButton.setOnClickListener(view -> mAdapter.clearAllNotifications()); 67 } 68 69 listView.addOnScrollListener(new OnScrollListener() { 70 @Override 71 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 72 super.onScrollStateChanged(recyclerView, newState); 73 74 // RecyclerView is not currently scrolling. 75 if (newState == RecyclerView.SCROLL_STATE_IDLE) { 76 setVisibleNotificationsAsSeen(); 77 } 78 } 79 }); 80 } 81 82 /** 83 * Updates notifications and update views. 84 */ setNotifications(List<NotificationGroup> notifications)85 public void setNotifications(List<NotificationGroup> notifications) { 86 mAdapter.setNotifications(notifications, /* setRecyclerViewListHeaderAndFooter= */ true); 87 } 88 89 /** 90 * Collapses all expanded groups. 91 */ collapseAllGroups()92 public void collapseAllGroups() { 93 mAdapter.collapseAllGroups(); 94 } 95 96 @Override onUxRestrictionsChanged(CarUxRestrictions restrictionInfo)97 public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) { 98 mAdapter.setCarUxRestrictions(restrictionInfo); 99 } 100 101 /** 102 * Sets the NotificationClickHandlerFactory that allows for a hook to run a block off code 103 * when the notification is clicked. This is useful to dismiss a screen after 104 * a notification list clicked. 105 */ setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory)106 public void setClickHandlerFactory(NotificationClickHandlerFactory clickHandlerFactory) { 107 mAdapter.setClickHandlerFactory(clickHandlerFactory); 108 } 109 110 /** 111 * Sets NotificationDataManager that handles additional states for notifications such as "seen", 112 * and muting a messaging type notification. 113 * 114 * @param notificationDataManager An instance of NotificationDataManager. 115 */ setNotificationDataManager(NotificationDataManager notificationDataManager)116 public void setNotificationDataManager(NotificationDataManager notificationDataManager) { 117 mNotificationDataManager = notificationDataManager; 118 mAdapter.setNotificationDataManager(notificationDataManager); 119 } 120 121 /** 122 * A {@link RecyclerView.ItemDecoration} that will add a top offset to the first item and bottom 123 * offset to the last item in the RecyclerView it is added to. 124 */ 125 private static class TopAndBottomOffsetDecoration extends RecyclerView.ItemDecoration { 126 private int mTopAndBottomOffset; 127 TopAndBottomOffsetDecoration(int topOffset)128 private TopAndBottomOffsetDecoration(int topOffset) { 129 mTopAndBottomOffset = topOffset; 130 } 131 132 @Override getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)133 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 134 RecyclerView.State state) { 135 super.getItemOffsets(outRect, view, parent, state); 136 int position = parent.getChildAdapterPosition(view); 137 138 if (position == 0) { 139 outRect.top = mTopAndBottomOffset; 140 } 141 if (position == state.getItemCount() - 1) { 142 outRect.bottom = mTopAndBottomOffset; 143 } 144 } 145 } 146 147 /** 148 * A {@link RecyclerView.ItemDecoration} that will add spacing between each item in the 149 * RecyclerView that it is added to. 150 */ 151 private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration { 152 private int mItemSpacing; 153 ItemSpacingDecoration(int itemSpacing)154 private ItemSpacingDecoration(int itemSpacing) { 155 mItemSpacing = itemSpacing; 156 } 157 158 @Override getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)159 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 160 RecyclerView.State state) { 161 super.getItemOffsets(outRect, view, parent, state); 162 int position = parent.getChildAdapterPosition(view); 163 164 // Skip offset for last item. 165 if (position == state.getItemCount() - 1) { 166 return; 167 } 168 169 outRect.bottom = mItemSpacing; 170 } 171 } 172 173 /** 174 * Sets currently visible notifications as "seen". 175 */ setVisibleNotificationsAsSeen()176 public void setVisibleNotificationsAsSeen() { 177 int firstVisible = mLayoutManager.findFirstVisibleItemPosition(); 178 int lastVisible = mLayoutManager.findLastVisibleItemPosition(); 179 180 // No visible items are found. 181 if (firstVisible == RecyclerView.NO_POSITION) return; 182 183 184 for (int i = firstVisible; i <= lastVisible; i++) { 185 mAdapter.setNotificationAsSeen(i); 186 } 187 } 188 } 189