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