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