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