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 = 108 NotificationUtils.getAttrColor(getContext(), android.R.attr.textColorPrimary); 109 mDefaultSecondaryTextColor = 110 NotificationUtils.getAttrColor(getContext(), android.R.attr.textColorSecondary); 111 mDefaultUseLauncherIcon = getResources().getBoolean(R.bool.config_useLauncherIcon); 112 } 113 init(AttributeSet attrs)114 private void init(AttributeSet attrs) { 115 if (attrs != null) { 116 TypedArray attributes = 117 getContext().obtainStyledAttributes(attrs, R.styleable.CarNotificationBodyView); 118 mShowBigIcon = 119 attributes.getBoolean(R.styleable.CarNotificationBodyView_showBigIcon, 120 /* defValue= */ false); 121 mMaxLines = attributes.getInteger(R.styleable.CarNotificationBodyView_maxLines, 122 /* defValue= */ DEFAULT_MAX_LINES); 123 mIsHeadsUp = 124 attributes.getBoolean(R.styleable.CarNotificationBodyView_isHeadsUp, 125 /* defValue= */ false); 126 attributes.recycle(); 127 } 128 129 inflate(getContext(), mIsHeadsUp ? R.layout.car_headsup_notification_body_view 130 : R.layout.car_notification_body_view, /* root= */ this); 131 } 132 133 @Override onFinishInflate()134 protected void onFinishInflate() { 135 super.onFinishInflate(); 136 mTitleView = findViewById(R.id.notification_body_title); 137 mTitleIconView = findViewById(R.id.notification_body_title_icon); 138 mContentView = findViewById(R.id.notification_body_content); 139 mLargeIconView = findViewById(R.id.notification_body_icon); 140 mCountView = findViewById(R.id.message_count); 141 mTimeView = findViewById(R.id.time); 142 if (mTimeView != null) { 143 mTimeView.setShowRelativeTime(true); 144 } 145 } 146 147 /** 148 * Binds the notification body. 149 * 150 * @param title the primary text 151 * @param content the secondary text, if this is null then content view will be hidden 152 * @param launcherIcon the launcher icon drawable for notification's package. 153 * If this and largeIcon are null then large icon view will be hidden. 154 * @param largeIcon the large icon, usually used for avatars. 155 * If this and launcherIcon are null then large icon view will be hidden. 156 * @param countText text signifying the number of messages inside this notification 157 * @param when wall clock time in milliseconds for the notification 158 */ bind(CharSequence title, @Nullable CharSequence content, StatusBarNotification sbn, @Nullable Icon largeIcon, @Nullable Drawable titleIcon, @Nullable CharSequence countText, @Nullable Long when)159 public void bind(CharSequence title, @Nullable CharSequence content, 160 StatusBarNotification sbn, @Nullable Icon largeIcon, @Nullable Drawable titleIcon, 161 @Nullable CharSequence countText, @Nullable Long when) { 162 setVisibility(View.VISIBLE); 163 164 boolean useLauncherIcon = setUseLauncherIcon(sbn); 165 Drawable launcherIcon = loadAppLauncherIcon(sbn); 166 if (mLargeIconView != null) { 167 if (useLauncherIcon && launcherIcon != null) { 168 mLargeIconView.setVisibility(View.VISIBLE); 169 mLargeIconView.setImageDrawable(launcherIcon); 170 } else if (!useLauncherIcon && (mShowBigIcon || mDefaultUseLauncherIcon)) { 171 if (largeIcon != null) { 172 largeIcon.loadDrawableAsync(getContext(), drawable -> { 173 mLargeIconView.setVisibility(View.VISIBLE); 174 mLargeIconView.setImageDrawable(drawable); 175 }, Handler.createAsync(Looper.myLooper())); 176 } else { 177 Log.w(TAG, "Notification with title=" + title 178 + " did not specify a large icon"); 179 } 180 } else { 181 mLargeIconView.setVisibility(View.GONE); 182 } 183 } 184 185 if (mTitleView != null) { 186 mTitleView.setVisibility(View.VISIBLE); 187 mTitleView.setText(title); 188 } 189 190 if (mTitleIconView != null && titleIcon != null) { 191 mTitleIconView.setVisibility(View.VISIBLE); 192 mTitleIconView.setImageDrawable(titleIcon); 193 } 194 195 if (mContentView != null) { 196 if (!TextUtils.isEmpty(content)) { 197 mContentView.setVisibility(View.VISIBLE); 198 mContentView.setMaxLines(mMaxLines); 199 mContentView.setText(content); 200 } else { 201 mContentView.setVisibility(View.GONE); 202 } 203 } 204 205 // optional field: time 206 if (mTimeView != null) { 207 if (when != null && !mIsHeadsUp) { 208 mTimeView.setVisibility(View.VISIBLE); 209 mTimeView.setTime(when); 210 } else { 211 mTimeView.setVisibility(View.GONE); 212 } 213 } 214 215 if (mCountView != null) { 216 if (countText != null) { 217 mCountView.setVisibility(View.VISIBLE); 218 mCountView.setText(countText); 219 } else { 220 mCountView.setVisibility(View.GONE); 221 } 222 } 223 } 224 225 /** 226 * Sets the primary text color. 227 */ setSecondaryTextColor(@olorInt int color)228 public void setSecondaryTextColor(@ColorInt int color) { 229 if (mContentView != null) { 230 mContentView.setTextColor(color); 231 } 232 } 233 234 /** 235 * Sets max lines for the content view. 236 */ setContentMaxLines(int maxLines)237 public void setContentMaxLines(int maxLines) { 238 if (mContentView != null) { 239 mContentView.setMaxLines(maxLines); 240 } 241 } 242 243 /** 244 * Sets the secondary text color. 245 */ setPrimaryTextColor(@olorInt int color)246 public void setPrimaryTextColor(@ColorInt int color) { 247 if (mTitleView != null) { 248 mTitleView.setTextColor(color); 249 } 250 } 251 252 /** 253 * Sets the text color for the count field. 254 */ setCountTextColor(@olorInt int color)255 public void setCountTextColor(@ColorInt int color) { 256 if (mCountView != null) { 257 mCountView.setTextColor(color); 258 } 259 } 260 261 /** 262 * Sets the {@link OnClickListener} for the count field. 263 */ setCountOnClickListener(@ullable OnClickListener listener)264 public void setCountOnClickListener(@Nullable OnClickListener listener) { 265 if (mCountView != null) { 266 mCountView.setOnClickListener(listener); 267 } 268 } 269 270 /** 271 * Sets the text color for the time field. 272 */ setTimeTextColor(@olorInt int color)273 public void setTimeTextColor(@ColorInt int color) { 274 if (mTimeView != null) { 275 mTimeView.setTextColor(color); 276 } 277 } 278 279 /** 280 * Resets the notification actions empty for recycling. 281 */ reset()282 public void reset() { 283 setVisibility(View.GONE); 284 if (mTitleView != null) { 285 mTitleView.setVisibility(View.GONE); 286 } 287 if (mTitleIconView != null) { 288 mTitleIconView.setVisibility(View.GONE); 289 } 290 if (mContentView != null) { 291 setContentMaxLines(mMaxLines); 292 mContentView.setVisibility(View.GONE); 293 } 294 if (mLargeIconView != null) { 295 mLargeIconView.setVisibility(View.GONE); 296 } 297 setPrimaryTextColor(mDefaultPrimaryTextColor); 298 setSecondaryTextColor(mDefaultSecondaryTextColor); 299 if (mTimeView != null) { 300 mTimeView.setVisibility(View.GONE); 301 mTimeView.setTime(0); 302 setTimeTextColor(mDefaultPrimaryTextColor); 303 } 304 305 if (mCountView != null) { 306 mCountView.setVisibility(View.GONE); 307 mCountView.setText(null); 308 mCountView.setTextColor(mDefaultPrimaryTextColor); 309 } 310 } 311 312 /** 313 * Returns true if the launcher icon should be used for a given notification. 314 */ setUseLauncherIcon(StatusBarNotification sbn)315 private boolean setUseLauncherIcon(StatusBarNotification sbn) { 316 Bundle notificationExtras = sbn.getNotification().extras; 317 if (notificationExtras == null) { 318 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 319 } 320 321 if (notificationExtras.containsKey(EXTRA_USE_LAUNCHER_ICON) 322 && NotificationUtils.isSystemApp(getContext(), sbn)) { 323 return notificationExtras.getBoolean(EXTRA_USE_LAUNCHER_ICON); 324 } 325 return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon); 326 } 327 328 @Nullable loadAppLauncherIcon(StatusBarNotification sbn)329 private Drawable loadAppLauncherIcon(StatusBarNotification sbn) { 330 if (!setUseLauncherIcon(sbn)) { 331 return null; 332 } 333 Context packageContext = sbn.getPackageContext(getContext()); 334 PackageManager pm = packageContext.getPackageManager(); 335 return pm.getApplicationIcon(packageContext.getApplicationInfo()); 336 } 337 338 @VisibleForTesting getTitleView()339 TextView getTitleView() { 340 return mTitleView; 341 } 342 343 @VisibleForTesting getContentView()344 TextView getContentView() { 345 return mContentView; 346 } 347 348 @VisibleForTesting getCountView()349 TextView getCountView() { 350 return mCountView; 351 } 352 353 @VisibleForTesting getTimeView()354 DateTimeView getTimeView() { 355 return mTimeView; 356 } 357 } 358