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 mLargeIconView.setVisibility(View.GONE); 178 mLargeIconView.setImageDrawable(null); 179 } 180 } else { 181 mLargeIconView.setVisibility(View.GONE); 182 mLargeIconView.setImageDrawable(null); 183 } 184 } 185 186 if (mTitleView != null) { 187 if (!TextUtils.isEmpty(title)) { 188 mTitleView.setVisibility(View.VISIBLE); 189 mTitleView.setText(title); 190 } else { 191 mTitleView.setVisibility(View.GONE); 192 } 193 } 194 195 if (mTitleIconView != null) { 196 if (titleIcon != null) { 197 mTitleIconView.setVisibility(View.VISIBLE); 198 mTitleIconView.setImageDrawable(titleIcon); 199 } else { 200 mTitleIconView.setVisibility(View.GONE); 201 } 202 } 203 204 if (mContentView != null) { 205 if (!TextUtils.isEmpty(content)) { 206 mContentView.setVisibility(View.VISIBLE); 207 mContentView.setMaxLines(mMaxLines); 208 mContentView.setText(content); 209 } else { 210 mContentView.setVisibility(View.GONE); 211 } 212 } 213 214 // optional field: time 215 if (mTimeView != null) { 216 if (when != null && !mIsHeadsUp) { 217 mTimeView.setVisibility(View.VISIBLE); 218 mTimeView.setTime(when); 219 } else { 220 mTimeView.setVisibility(View.GONE); 221 } 222 } 223 224 if (mCountView != null) { 225 if (countText != null) { 226 mCountView.setVisibility(View.VISIBLE); 227 mCountView.setText(countText); 228 } else { 229 mCountView.setVisibility(View.GONE); 230 } 231 } 232 } 233 234 /** 235 * Sets the primary text color. 236 */ setSecondaryTextColor(@olorInt int color)237 public void setSecondaryTextColor(@ColorInt int color) { 238 if (mContentView != null) { 239 mContentView.setTextColor(color); 240 } 241 } 242 243 /** 244 * Sets max lines for the content view. 245 */ setContentMaxLines(int maxLines)246 public void setContentMaxLines(int maxLines) { 247 if (mContentView != null) { 248 mContentView.setMaxLines(maxLines); 249 } 250 } 251 252 /** 253 * Sets the secondary text color. 254 */ setPrimaryTextColor(@olorInt int color)255 public void setPrimaryTextColor(@ColorInt int color) { 256 if (mTitleView != null) { 257 mTitleView.setTextColor(color); 258 } 259 } 260 261 /** 262 * Sets the text color for the count field. 263 */ setCountTextColor(@olorInt int color)264 public void setCountTextColor(@ColorInt int color) { 265 if (mCountView != null) { 266 mCountView.setTextColor(color); 267 } 268 } 269 270 /** 271 * Sets the alpha for the count field. 272 */ setCountTextAlpha(float alpha)273 public void setCountTextAlpha(float alpha) { 274 if (mCountView != null) { 275 mCountView.setAlpha(alpha); 276 } 277 } 278 279 /** 280 * Sets the {@link OnClickListener} for the count field. 281 */ setCountOnClickListener(@ullable OnClickListener listener)282 public void setCountOnClickListener(@Nullable OnClickListener listener) { 283 if (mCountView != null) { 284 mCountView.setOnClickListener(listener); 285 } 286 } 287 288 /** 289 * Sets the text color for the time field. 290 */ setTimeTextColor(@olorInt int color)291 public void setTimeTextColor(@ColorInt int color) { 292 if (mTimeView != null) { 293 mTimeView.setTextColor(color); 294 } 295 } 296 297 /** 298 * Resets the notification actions empty for recycling. 299 */ reset()300 public void reset() { 301 setVisibility(View.GONE); 302 if (mTitleView != null) { 303 mTitleView.setVisibility(View.GONE); 304 } 305 if (mTitleIconView != null) { 306 mTitleIconView.setVisibility(View.GONE); 307 } 308 if (mContentView != null) { 309 setContentMaxLines(mMaxLines); 310 mContentView.setVisibility(View.GONE); 311 } 312 setPrimaryTextColor(mDefaultPrimaryTextColor); 313 setSecondaryTextColor(mDefaultSecondaryTextColor); 314 if (mTimeView != null) { 315 mTimeView.setVisibility(View.GONE); 316 mTimeView.setTime(0); 317 setTimeTextColor(mDefaultPrimaryTextColor); 318 } 319 320 if (mCountView != null) { 321 mCountView.setVisibility(View.GONE); 322 mCountView.setText(null); 323 mCountView.setTextColor(mDefaultPrimaryTextColor); 324 } 325 } 326 327 /** 328 * Returns true if the launcher icon should be used for a given notification. 329 */ setUseLauncherIcon(StatusBarNotification sbn)330 private boolean setUseLauncherIcon(StatusBarNotification sbn) { 331 Bundle notificationExtras = sbn.getNotification().extras; 332 if (notificationExtras == null) { 333 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 334 } 335 336 if (notificationExtras.containsKey(EXTRA_USE_LAUNCHER_ICON) 337 && NotificationUtils.isSystemApp(getContext(), sbn)) { 338 return notificationExtras.getBoolean(EXTRA_USE_LAUNCHER_ICON); 339 } 340 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 341 } 342 343 @Nullable loadAppLauncherIcon(StatusBarNotification sbn)344 private Drawable loadAppLauncherIcon(StatusBarNotification sbn) { 345 if (!setUseLauncherIcon(sbn)) { 346 return null; 347 } 348 Context packageContext = sbn.getPackageContext(getContext()); 349 PackageManager pm = packageContext.getPackageManager(); 350 return pm.getApplicationIcon(packageContext.getApplicationInfo()); 351 } 352 353 @VisibleForTesting getTitleView()354 TextView getTitleView() { 355 return mTitleView; 356 } 357 358 @VisibleForTesting getContentView()359 TextView getContentView() { 360 return mContentView; 361 } 362 363 @VisibleForTesting getCountView()364 TextView getCountView() { 365 return mCountView; 366 } 367 368 @VisibleForTesting getTimeView()369 DateTimeView getTimeView() { 370 return mTimeView; 371 } 372 } 373