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.ColorInt; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.content.res.TypedArray; 24 import android.graphics.drawable.Drawable; 25 import android.graphics.drawable.Icon; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.service.notification.StatusBarNotification; 30 import android.text.TextUtils; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.view.View; 34 import android.widget.DateTimeView; 35 import android.widget.ImageView; 36 import android.widget.RelativeLayout; 37 import android.widget.TextView; 38 39 import androidx.annotation.VisibleForTesting; 40 41 import com.android.car.notification.NotificationUtils; 42 import com.android.car.notification.R; 43 44 /** 45 * Common notification body that consists of a title line, a content text line, and an image icon on 46 * the end. 47 * 48 * <p> For example, for a messaging notification, the title is the sender's name, 49 * the content is the message, and the image icon is the sender's avatar. 50 */ 51 public class CarNotificationBodyView extends RelativeLayout { 52 private static final String TAG = "CarNotificationBodyView"; 53 private static final int DEFAULT_MAX_LINES = 3; 54 @ColorInt 55 private final int mDefaultPrimaryTextColor; 56 @ColorInt 57 private final int mDefaultSecondaryTextColor; 58 private final boolean mDefaultUseLauncherIcon; 59 60 /** 61 * Key that system apps can add to the Notification extras to override the default 62 * {@link R.bool.config_useLauncherIcon} behavior. If this is set to false, a small and a large 63 * icon should be specified to be shown properly in the relevant default configuration. 64 */ 65 @VisibleForTesting 66 static final String EXTRA_USE_LAUNCHER_ICON = 67 "com.android.car.notification.EXTRA_USE_LAUNCHER_ICON"; 68 69 private boolean mIsHeadsUp; 70 private boolean mShowBigIcon; 71 private int mMaxLines; 72 @Nullable 73 private TextView mTitleView; 74 @Nullable 75 private TextView mContentView; 76 @Nullable 77 private ImageView mLargeIconView; 78 @Nullable 79 private TextView mCountView; 80 @Nullable 81 private DateTimeView mTimeView; 82 @Nullable 83 private ImageView mTitleIconView; 84 CarNotificationBodyView(Context context)85 public CarNotificationBodyView(Context context) { 86 super(context); 87 init(/* attrs= */ null); 88 } 89 CarNotificationBodyView(Context context, AttributeSet attrs)90 public CarNotificationBodyView(Context context, AttributeSet attrs) { 91 super(context, attrs); 92 init(attrs); 93 } 94 CarNotificationBodyView(Context context, AttributeSet attrs, int defStyleAttr)95 public CarNotificationBodyView(Context context, AttributeSet attrs, int defStyleAttr) { 96 super(context, attrs, defStyleAttr); 97 init(attrs); 98 } 99 CarNotificationBodyView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)100 public CarNotificationBodyView( 101 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 102 super(context, attrs, defStyleAttr, defStyleRes); 103 init(attrs); 104 } 105 106 { 107 mDefaultPrimaryTextColor = getContext().getColor(R.color.primary_text_color); 108 mDefaultSecondaryTextColor = getContext().getColor(R.color.secondary_text_color); 109 mDefaultUseLauncherIcon = getResources().getBoolean(R.bool.config_useLauncherIcon); 110 } 111 init(AttributeSet attrs)112 private void init(AttributeSet attrs) { 113 if (attrs != null) { 114 TypedArray attributes = 115 getContext().obtainStyledAttributes(attrs, R.styleable.CarNotificationBodyView); 116 mShowBigIcon = 117 attributes.getBoolean(R.styleable.CarNotificationBodyView_showBigIcon, 118 /* defValue= */ false); 119 mMaxLines = attributes.getInteger(R.styleable.CarNotificationBodyView_maxLines, 120 /* defValue= */ DEFAULT_MAX_LINES); 121 mIsHeadsUp = 122 attributes.getBoolean(R.styleable.CarNotificationBodyView_isHeadsUp, 123 /* defValue= */ false); 124 attributes.recycle(); 125 } 126 127 inflate(getContext(), mIsHeadsUp ? R.layout.car_headsup_notification_body_view 128 : R.layout.car_notification_body_view, /* root= */ this); 129 } 130 131 @Override onFinishInflate()132 protected void onFinishInflate() { 133 super.onFinishInflate(); 134 mTitleView = findViewById(R.id.notification_body_title); 135 mTitleIconView = findViewById(R.id.notification_body_title_icon); 136 mContentView = findViewById(R.id.notification_body_content); 137 mLargeIconView = findViewById(R.id.notification_body_icon); 138 mCountView = findViewById(R.id.message_count); 139 mTimeView = findViewById(R.id.time); 140 if (mTimeView != null) { 141 mTimeView.setShowRelativeTime(true); 142 } 143 } 144 145 /** 146 * Binds the notification body. 147 * 148 * @param title the primary text 149 * @param content the secondary text, if this is null then content view will be hidden 150 * @param launcherIcon the launcher icon drawable for notification's package. 151 * If this and largeIcon are null then large icon view will be hidden. 152 * @param largeIcon the large icon, usually used for avatars. 153 * If this and launcherIcon are null then large icon view will be hidden. 154 * @param countText text signifying the number of messages inside this notification 155 * @param when wall clock time in milliseconds for the notification 156 */ bind(CharSequence title, @Nullable CharSequence content, StatusBarNotification sbn, @Nullable Icon largeIcon, @Nullable Drawable titleIcon, @Nullable CharSequence countText, @Nullable Long when)157 public void bind(CharSequence title, @Nullable CharSequence content, 158 StatusBarNotification sbn, @Nullable Icon largeIcon, @Nullable Drawable titleIcon, 159 @Nullable CharSequence countText, @Nullable Long when) { 160 setVisibility(View.VISIBLE); 161 162 boolean useLauncherIcon = setUseLauncherIcon(sbn); 163 Drawable launcherIcon = loadAppLauncherIcon(sbn); 164 if (mLargeIconView != null) { 165 if (useLauncherIcon && launcherIcon != null) { 166 mLargeIconView.setVisibility(View.VISIBLE); 167 mLargeIconView.setImageDrawable(launcherIcon); 168 } else if (!useLauncherIcon && (mShowBigIcon || mDefaultUseLauncherIcon)) { 169 if (largeIcon != null) { 170 largeIcon.loadDrawableAsync(getContext(), drawable -> { 171 mLargeIconView.setVisibility(View.VISIBLE); 172 mLargeIconView.setImageDrawable(drawable); 173 }, Handler.createAsync(Looper.myLooper())); 174 } else { 175 Log.w(TAG, "Notification with title=" + title 176 + " did not specify a large icon"); 177 } 178 } else { 179 mLargeIconView.setVisibility(View.GONE); 180 } 181 } 182 183 if (mTitleView != null) { 184 mTitleView.setVisibility(View.VISIBLE); 185 mTitleView.setText(title); 186 } 187 188 if (mTitleIconView != null && titleIcon != null) { 189 mTitleIconView.setVisibility(View.VISIBLE); 190 mTitleIconView.setImageDrawable(titleIcon); 191 } 192 193 if (mContentView != null) { 194 if (!TextUtils.isEmpty(content)) { 195 mContentView.setVisibility(View.VISIBLE); 196 mContentView.setMaxLines(mMaxLines); 197 mContentView.setText(content); 198 } else { 199 mContentView.setVisibility(View.GONE); 200 } 201 } 202 203 // optional field: time 204 if (mTimeView != null) { 205 if (when != null && !mIsHeadsUp) { 206 mTimeView.setVisibility(View.VISIBLE); 207 mTimeView.setTime(when); 208 } else { 209 mTimeView.setVisibility(View.GONE); 210 } 211 } 212 213 if (mCountView != null) { 214 if (countText != null) { 215 mCountView.setVisibility(View.VISIBLE); 216 mCountView.setText(countText); 217 } else { 218 mCountView.setVisibility(View.GONE); 219 } 220 } 221 } 222 223 /** 224 * Sets the primary text color. 225 */ setSecondaryTextColor(@olorInt int color)226 public void setSecondaryTextColor(@ColorInt int color) { 227 if (mContentView != null) { 228 mContentView.setTextColor(color); 229 } 230 } 231 232 /** 233 * Sets max lines for the content view. 234 */ setContentMaxLines(int maxLines)235 public void setContentMaxLines(int maxLines) { 236 if (mContentView != null) { 237 mContentView.setMaxLines(maxLines); 238 } 239 } 240 241 /** 242 * Sets the secondary text color. 243 */ setPrimaryTextColor(@olorInt int color)244 public void setPrimaryTextColor(@ColorInt int color) { 245 if (mTitleView != null) { 246 mTitleView.setTextColor(color); 247 } 248 } 249 250 /** 251 * Sets the text color for the count field. 252 */ setCountTextColor(@olorInt int color)253 public void setCountTextColor(@ColorInt int color) { 254 if (mCountView != null) { 255 mCountView.setTextColor(color); 256 } 257 } 258 259 /** 260 * Sets the alpha for the count field. 261 */ setCountTextAlpha(float alpha)262 public void setCountTextAlpha(float alpha) { 263 if (mCountView != null) { 264 mCountView.setAlpha(alpha); 265 } 266 } 267 268 /** 269 * Sets the {@link OnClickListener} for the count field. 270 */ setCountOnClickListener(@ullable OnClickListener listener)271 public void setCountOnClickListener(@Nullable OnClickListener listener) { 272 if (mCountView != null) { 273 mCountView.setOnClickListener(listener); 274 } 275 } 276 277 /** 278 * Sets the text color for the time field. 279 */ setTimeTextColor(@olorInt int color)280 public void setTimeTextColor(@ColorInt int color) { 281 if (mTimeView != null) { 282 mTimeView.setTextColor(color); 283 } 284 } 285 286 /** 287 * Resets the notification actions empty for recycling. 288 */ reset()289 public void reset() { 290 setVisibility(View.GONE); 291 if (mTitleView != null) { 292 mTitleView.setVisibility(View.GONE); 293 } 294 if (mTitleIconView != null) { 295 mTitleIconView.setVisibility(View.GONE); 296 } 297 if (mContentView != null) { 298 setContentMaxLines(mMaxLines); 299 mContentView.setVisibility(View.GONE); 300 } 301 if (mLargeIconView != null) { 302 mLargeIconView.setVisibility(View.GONE); 303 } 304 setPrimaryTextColor(mDefaultPrimaryTextColor); 305 setSecondaryTextColor(mDefaultSecondaryTextColor); 306 if (mTimeView != null) { 307 mTimeView.setVisibility(View.GONE); 308 mTimeView.setTime(0); 309 setTimeTextColor(mDefaultPrimaryTextColor); 310 } 311 312 if (mCountView != null) { 313 mCountView.setVisibility(View.GONE); 314 mCountView.setText(null); 315 mCountView.setTextColor(mDefaultPrimaryTextColor); 316 } 317 } 318 319 /** 320 * Returns true if the launcher icon should be used for a given notification. 321 */ setUseLauncherIcon(StatusBarNotification sbn)322 private boolean setUseLauncherIcon(StatusBarNotification sbn) { 323 Bundle notificationExtras = sbn.getNotification().extras; 324 if (notificationExtras == null) { 325 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 326 } 327 328 if (notificationExtras.containsKey(EXTRA_USE_LAUNCHER_ICON) 329 && NotificationUtils.isSystemApp(getContext(), sbn)) { 330 return notificationExtras.getBoolean(EXTRA_USE_LAUNCHER_ICON); 331 } 332 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 333 } 334 335 @Nullable loadAppLauncherIcon(StatusBarNotification sbn)336 private Drawable loadAppLauncherIcon(StatusBarNotification sbn) { 337 if (!setUseLauncherIcon(sbn)) { 338 return null; 339 } 340 Context packageContext = sbn.getPackageContext(getContext()); 341 PackageManager pm = packageContext.getPackageManager(); 342 return pm.getApplicationIcon(packageContext.getApplicationInfo()); 343 } 344 345 @VisibleForTesting getTitleView()346 TextView getTitleView() { 347 return mTitleView; 348 } 349 350 @VisibleForTesting getContentView()351 TextView getContentView() { 352 return mContentView; 353 } 354 355 @VisibleForTesting getCountView()356 TextView getCountView() { 357 return mCountView; 358 } 359 360 @VisibleForTesting getTimeView()361 DateTimeView getTimeView() { 362 return mTimeView; 363 } 364 } 365