1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.notification.template;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.ColorInt;
21 import android.annotation.Nullable;
22 import android.app.Notification;
23 import android.content.Context;
24 import android.service.notification.StatusBarNotification;
25 import android.view.View;
26 
27 import androidx.cardview.widget.CardView;
28 import androidx.recyclerview.widget.RecyclerView;
29 
30 import com.android.car.notification.NotificationClickHandlerFactory;
31 import com.android.car.notification.NotificationUtils;
32 import com.android.car.notification.R;
33 import com.android.car.notification.ThemesUtil;
34 
35 /**
36  * The base view holder class that all template view holders should extend.
37  */
38 public abstract class CarNotificationBaseViewHolder extends RecyclerView.ViewHolder {
39     private final Context mContext;
40     private final NotificationClickHandlerFactory mClickHandlerFactory;
41 
42     @Nullable
43     private final CardView mCardView; // can be null for group child or group summary notification
44     @Nullable
45     private final View mInnerView; // can be null for GroupNotificationViewHolder
46     @Nullable
47     private final CarNotificationHeaderView mHeaderView;
48     @Nullable
49     private final CarNotificationBodyView mBodyView;
50     @Nullable
51     private final CarNotificationActionsView mActionsView;
52 
53     @ColorInt
54     private final int mDefaultBackgroundColor;
55     @ColorInt
56     private final int mDefaultCarAccentColor;
57     @ColorInt
58     private final int mDefaultPrimaryForegroundColor;
59     @ColorInt
60     private final int mDefaultSecondaryForegroundColor;
61     @ColorInt
62     private int mCalculatedPrimaryForegroundColor;
63     @ColorInt
64     private int mCalculatedSecondaryForegroundColor;
65     @ColorInt
66     private int mSmallIconColor;
67     @ColorInt
68     private int mBackgroundColor;
69 
70     private StatusBarNotification mStatusBarNotification;
71     private boolean mIsAnimating;
72     private boolean mHasColor;
73     private boolean mIsColorized;
74     private boolean mEnableCardBackgroundColorForCategoryNavigation;
75     private boolean mEnableCardBackgroundColorForSystemApp;
76     private boolean mEnableSmallIconAccentColor;
77 
78     /**
79      * Tracks if the foreground colors have been calculated for the binding of the view holder.
80      * The colors should only be calculated once per binding.
81      **/
82     private boolean mInitializedColors;
83 
CarNotificationBaseViewHolder( View itemView, NotificationClickHandlerFactory clickHandlerFactory)84     CarNotificationBaseViewHolder(
85             View itemView, NotificationClickHandlerFactory clickHandlerFactory) {
86         super(itemView);
87         mContext = itemView.getContext();
88         mClickHandlerFactory = clickHandlerFactory;
89         mCardView = itemView.findViewById(R.id.card_view);
90         mInnerView = itemView.findViewById(R.id.inner_template_view);
91         mHeaderView = itemView.findViewById(R.id.notification_header);
92         mBodyView = itemView.findViewById(R.id.notification_body);
93         mActionsView = itemView.findViewById(R.id.notification_actions);
94         mDefaultBackgroundColor = ThemesUtil.getAttrColor(mContext, android.R.attr.colorPrimary);
95         mDefaultCarAccentColor = ThemesUtil.getAttrColor(mContext, android.R.attr.colorAccent);
96         mDefaultPrimaryForegroundColor = mContext.getColor(R.color.primary_text_color);
97         mDefaultSecondaryForegroundColor = mContext.getColor(R.color.secondary_text_color);
98         mEnableCardBackgroundColorForCategoryNavigation =
99                 mContext.getResources().getBoolean(
100                         R.bool.config_enableCardBackgroundColorForCategoryNavigation);
101         mEnableCardBackgroundColorForSystemApp =
102                 mContext.getResources().getBoolean(
103                         R.bool.config_enableCardBackgroundColorForSystemApp);
104         mEnableSmallIconAccentColor =
105                 mContext.getResources().getBoolean(R.bool.config_enableSmallIconAccentColor);
106     }
107 
108     /**
109      * Binds a {@link StatusBarNotification} to a notification template. Base class sets the
110      * clicking event for the card view and calls recycling methods.
111      *
112      * @param statusBarNotification the notification to be bound.
113      * @param isInGroup whether this notification is part of a grouped notification.
114      */
115     @CallSuper
bind(StatusBarNotification statusBarNotification, boolean isInGroup, boolean isHeadsUp)116     public void bind(StatusBarNotification statusBarNotification, boolean isInGroup,
117             boolean isHeadsUp) {
118         reset();
119         mStatusBarNotification = statusBarNotification;
120 
121         if (isInGroup) {
122             mInnerView.setBackgroundColor(mDefaultBackgroundColor);
123             mInnerView.setOnClickListener(
124                     mClickHandlerFactory.getClickHandler(mStatusBarNotification));
125         } else {
126             mCardView.setOnClickListener(
127                     mClickHandlerFactory.getClickHandler(mStatusBarNotification));
128         }
129 
130         bindCardView(mCardView, isInGroup);
131         bindHeader(mHeaderView, isInGroup);
132         bindBody(mBodyView, isInGroup);
133     }
134 
135     /**
136      * Binds a {@link StatusBarNotification} to a notification template's card.
137      *
138      * @param cardView the CardView the notification should be bound to.
139      * @param isInGroup whether this notification is part of a grouped notification.
140      */
bindCardView(CardView cardView, boolean isInGroup)141     void bindCardView(CardView cardView, boolean isInGroup) {
142         initializeColors(isInGroup);
143 
144         if (canChangeCardBackgroundColor() && mHasColor && mIsColorized && !isInGroup) {
145             cardView.setCardBackgroundColor(mBackgroundColor);
146         }
147     }
148 
149     /**
150      * Binds a {@link StatusBarNotification} to a notification template's header.
151      *
152      * @param headerView the CarNotificationHeaderView the notification should be bound to.
153      * @param isInGroup whether this notification is part of a grouped notification.
154      */
bindHeader(CarNotificationHeaderView headerView, boolean isInGroup)155     void bindHeader(CarNotificationHeaderView headerView, boolean isInGroup) {
156         if (headerView == null) return;
157         initializeColors(isInGroup);
158 
159         headerView.setSmallIconColor(mSmallIconColor);
160         headerView.setHeaderTextColor(mCalculatedPrimaryForegroundColor);
161         headerView.setTimeTextColor(mCalculatedPrimaryForegroundColor);
162     }
163 
164     /**
165      * Binds a {@link StatusBarNotification} to a notification template's body.
166      *
167      * @param bodyView the CarNotificationBodyView the notification should be bound to.
168      * @param isInGroup whether this notification is part of a grouped notification.
169      */
bindBody(CarNotificationBodyView bodyView, boolean isInGroup)170     void bindBody(CarNotificationBodyView bodyView,
171             boolean isInGroup) {
172         if (bodyView == null) return;
173         initializeColors(isInGroup);
174 
175         bodyView.setPrimaryTextColor(mCalculatedPrimaryForegroundColor);
176         bodyView.setSecondaryTextColor(mCalculatedSecondaryForegroundColor);
177     }
178 
initializeColors(boolean isInGroup)179     private void initializeColors(boolean isInGroup) {
180         if (mInitializedColors) return;
181         Notification notification = getStatusBarNotification().getNotification();
182 
183         mHasColor = notification.color != Notification.COLOR_DEFAULT;
184         mIsColorized = notification.extras.getBoolean(Notification.EXTRA_COLORIZED, false);
185 
186         mCalculatedPrimaryForegroundColor = mDefaultPrimaryForegroundColor;
187         mCalculatedSecondaryForegroundColor = mDefaultSecondaryForegroundColor;
188         if (canChangeCardBackgroundColor() && mHasColor && mIsColorized && !isInGroup) {
189             mBackgroundColor = notification.color;
190             mCalculatedPrimaryForegroundColor = NotificationColorUtil.resolveContrastColor(
191                     mDefaultPrimaryForegroundColor, mBackgroundColor);
192             mCalculatedSecondaryForegroundColor = NotificationColorUtil.resolveContrastColor(
193                     mDefaultSecondaryForegroundColor, mBackgroundColor);
194         }
195         mSmallIconColor =
196                 hasCustomBackgroundColor() ? mCalculatedPrimaryForegroundColor : getAccentColor();
197 
198         mInitializedColors = true;
199     }
200 
canChangeCardBackgroundColor()201     private boolean canChangeCardBackgroundColor() {
202         Notification notification = getStatusBarNotification().getNotification();
203 
204         boolean isSystemApp = mEnableCardBackgroundColorForSystemApp &&
205                 NotificationUtils.isSystemApp(mContext, getStatusBarNotification());
206         boolean isSignedWithPlatformKey = NotificationUtils.isSignedWithPlatformKey(mContext,
207                 getStatusBarNotification());
208         boolean isNavigationCategory = mEnableCardBackgroundColorForCategoryNavigation &&
209                 Notification.CATEGORY_NAVIGATION.equals(notification.category);
210         return isSystemApp || isNavigationCategory || isSignedWithPlatformKey;
211     }
212 
213     /**
214      * Returns the accent color for this notification.
215      */
216     @ColorInt
getAccentColor()217     int getAccentColor() {
218         int color = getStatusBarNotification().getNotification().color;
219         if (mEnableSmallIconAccentColor && color != Notification.COLOR_DEFAULT) {
220             return color;
221         }
222         return mDefaultCarAccentColor;
223     }
224 
225     /**
226      * Returns whether this card has a custom background color.
227      */
hasCustomBackgroundColor()228     boolean hasCustomBackgroundColor() {
229         return mBackgroundColor != mDefaultBackgroundColor;
230     }
231 
232     /**
233      * Child view holders should override and call super to recycle any custom component
234      * that's not handled by {@link CarNotificationHeaderView}, {@link CarNotificationBodyView} and
235      * {@link CarNotificationActionsView}.
236      * Note that any child class that is not calling {@link #bind} has to call this method directly.
237      */
238     @CallSuper
reset()239     void reset() {
240         mStatusBarNotification = null;
241         mBackgroundColor = mDefaultBackgroundColor;
242         mInitializedColors = false;
243 
244         itemView.setTranslationX(0);
245         itemView.setAlpha(1f);
246 
247         if (mCardView != null) {
248             mCardView.setOnClickListener(null);
249             mCardView.setCardBackgroundColor(mDefaultBackgroundColor);
250         }
251 
252         if (mHeaderView != null) {
253             mHeaderView.reset();
254         }
255 
256         if (mBodyView != null) {
257             mBodyView.reset();
258         }
259 
260         if (mActionsView != null) {
261             mActionsView.reset();
262         }
263     }
264 
265     /**
266      * Returns the current {@link StatusBarNotification} that this view holder is holding.
267      * Note that any child class that is not calling {@link #bind} has to override this method.
268      */
getStatusBarNotification()269     public StatusBarNotification getStatusBarNotification() {
270         return mStatusBarNotification;
271     }
272 
273     /**
274      * Returns true if the notification contained in this view holder can be swiped away.
275      */
isDismissible()276     public boolean isDismissible() {
277         if (mStatusBarNotification == null) {
278             return true;
279         }
280 
281         return (mStatusBarNotification.getNotification().flags
282                 & (Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_ONGOING_EVENT)) == 0;
283     }
284 
285     /**
286      * Returns the TranslationX of the ItemView.
287      */
getSwipeTranslationX()288     public float getSwipeTranslationX() {
289         return itemView.getTranslationX();
290     }
291 
292     /**
293      * Sets the TranslationX of the ItemView.
294      */
setSwipeTranslationX(float translationX)295     public void setSwipeTranslationX(float translationX) {
296         itemView.setTranslationX(translationX);
297     }
298 
299     /**
300      * Sets the alpha of the ItemView.
301      */
setSwipeAlpha(float alpha)302     public void setSwipeAlpha(float alpha) {
303         itemView.setAlpha(alpha);
304     }
305 
306     /**
307      * Sets whether this view holder has ongoing animation.
308      */
setIsAnimating(boolean animating)309     public void setIsAnimating(boolean animating) {
310         mIsAnimating = animating;
311     }
312 
313     /**
314      * Returns true if this view holder has ongoing animation.
315      */
isAnimating()316     public boolean isAnimating() {
317         return mIsAnimating;
318     }
319 }
320