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.res.TypedArray;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.Icon;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.text.TextUtils;
28 import android.util.AttributeSet;
29 import android.view.View;
30 import android.widget.DateTimeView;
31 import android.widget.ImageView;
32 import android.widget.RelativeLayout;
33 import android.widget.TextView;
34 
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.car.notification.NotificationUtils;
38 import com.android.car.notification.R;
39 
40 /**
41  * Common notification body that consists of a title line, a content text line, and an image icon on
42  * the end.
43  *
44  * <p> For example, for a messaging notification, the title is the sender's name,
45  * the content is the message, and the image icon is the sender's avatar.
46  */
47 public class CarNotificationBodyView extends RelativeLayout {
48     private static final int DEFAULT_MAX_LINES = 3;
49     @ColorInt
50     private final int mDefaultPrimaryTextColor;
51     @ColorInt
52     private final int mDefaultSecondaryTextColor;
53     private final boolean mUseLauncherIcon;
54 
55     private boolean mIsHeadsUp;
56     private boolean mShowBigIcon;
57     private int mMaxLines;
58     @Nullable
59     private TextView mTitleView;
60     @Nullable
61     private TextView mContentView;
62     @Nullable
63     private ImageView mLargeIconView;
64     @Nullable
65     private TextView mCountView;
66     @Nullable
67     private DateTimeView mTimeView;
68     @Nullable
69     private ImageView mTitleIconView;
70 
CarNotificationBodyView(Context context)71     public CarNotificationBodyView(Context context) {
72         super(context);
73     }
74 
CarNotificationBodyView(Context context, AttributeSet attrs)75     public CarNotificationBodyView(Context context, AttributeSet attrs) {
76         super(context, attrs);
77         init(attrs);
78     }
79 
CarNotificationBodyView(Context context, AttributeSet attrs, int defStyleAttr)80     public CarNotificationBodyView(Context context, AttributeSet attrs, int defStyleAttr) {
81         super(context, attrs, defStyleAttr);
82         init(attrs);
83     }
84 
CarNotificationBodyView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)85     public CarNotificationBodyView(
86             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
87         super(context, attrs, defStyleAttr, defStyleRes);
88         init(attrs);
89     }
90 
91     {
92         mDefaultPrimaryTextColor =
93                 NotificationUtils.getAttrColor(getContext(), android.R.attr.textColorPrimary);
94         mDefaultSecondaryTextColor =
95                 NotificationUtils.getAttrColor(getContext(), android.R.attr.textColorSecondary);
96         mUseLauncherIcon = getResources().getBoolean(R.bool.config_useLauncherIcon);
97     }
98 
init(AttributeSet attrs)99     private void init(AttributeSet attrs) {
100         TypedArray attributes =
101                 getContext().obtainStyledAttributes(attrs, R.styleable.CarNotificationBodyView);
102         mShowBigIcon = attributes.getBoolean(R.styleable.CarNotificationBodyView_showBigIcon,
103                 /* defValue= */ false);
104         mMaxLines = attributes.getInteger(R.styleable.CarNotificationBodyView_maxLines,
105                 /* defValue= */ DEFAULT_MAX_LINES);
106         mIsHeadsUp = attributes.getBoolean(R.styleable.CarNotificationHeaderView_isHeadsUp,
107                 /* defValue= */ false);
108         inflate(getContext(), mIsHeadsUp ? R.layout.car_headsup_notification_body_view
109                 : R.layout.car_notification_body_view, /* root= */ this);
110         attributes.recycle();
111     }
112 
113     @Override
onFinishInflate()114     protected void onFinishInflate() {
115         super.onFinishInflate();
116         mTitleView = findViewById(R.id.notification_body_title);
117         mTitleIconView = findViewById(R.id.notification_body_title_icon);
118         mContentView = findViewById(R.id.notification_body_content);
119         mLargeIconView = findViewById(R.id.notification_body_icon);
120         mCountView = findViewById(R.id.message_count);
121         mTimeView = findViewById(R.id.time);
122         if (mTimeView != null) {
123             mTimeView.setShowRelativeTime(true);
124         }
125     }
126 
127     /**
128      * Binds the notification body.
129      *
130      * @param title     the primary text
131      * @param content   the secondary text, if this is null then content view will be hidden
132      * @param launcherIcon  the launcher icon drawable for notification's package.
133      *        If this and largeIcon are null then large icon view will be hidden.
134      * @param largeIcon the large icon, usually used for avatars.
135      *        If this and launcherIcon are null then large icon view will be hidden.
136      * @param countText text signifying the number of messages inside this notification
137      * @param when      wall clock time in milliseconds for the notification
138      */
bind(CharSequence title, @Nullable CharSequence content, @Nullable Drawable launcherIcon, @Nullable Icon largeIcon, @Nullable Drawable titleIcon, @Nullable CharSequence countText, @Nullable Long when)139     public void bind(CharSequence title, @Nullable CharSequence content,
140             @Nullable Drawable launcherIcon, @Nullable Icon largeIcon, @Nullable Drawable titleIcon,
141             @Nullable CharSequence countText, @Nullable Long when) {
142         setVisibility(View.VISIBLE);
143 
144         if (mLargeIconView != null && largeIcon != null && !mUseLauncherIcon && mShowBigIcon) {
145             largeIcon.loadDrawableAsync(getContext(), drawable -> {
146                 mLargeIconView.setVisibility(View.VISIBLE);
147                 mLargeIconView.setImageDrawable(drawable);
148             }, Handler.createAsync(Looper.myLooper()));
149         } else if (mLargeIconView != null && launcherIcon != null && mUseLauncherIcon) {
150             mLargeIconView.setVisibility(View.VISIBLE);
151             mLargeIconView.setImageDrawable(launcherIcon);
152         } else if (mLargeIconView != null) {
153             mLargeIconView.setVisibility(View.GONE);
154         }
155 
156         if (mTitleView != null) {
157             mTitleView.setVisibility(View.VISIBLE);
158             mTitleView.setText(title);
159         }
160 
161         if (mTitleIconView != null && titleIcon != null) {
162             mTitleIconView.setVisibility(View.VISIBLE);
163             mTitleIconView.setImageDrawable(titleIcon);
164         }
165 
166         if (mContentView != null && !TextUtils.isEmpty(content)) {
167             mContentView.setVisibility(View.VISIBLE);
168             mContentView.setMaxLines(mMaxLines);
169             mContentView.setText(content);
170         } else if (mContentView != null) {
171             mContentView.setVisibility(View.GONE);
172         }
173 
174         if (mTimeView != null && when != null && !mIsHeadsUp) {
175             mTimeView.setVisibility(View.VISIBLE);
176             mTimeView.setTime(when);
177         } else if (mTimeView != null) {
178             mTimeView.setVisibility(View.GONE);
179         }
180 
181         if (mCountView != null && countText != null) {
182             mCountView.setVisibility(View.VISIBLE);
183             mCountView.setText(countText);
184         } else if (mCountView != null) {
185             mCountView.setVisibility(View.GONE);
186         }
187     }
188 
189     /**
190      * Sets the secondary text color.
191      */
setSecondaryTextColor(@olorInt int color)192     public void setSecondaryTextColor(@ColorInt int color) {
193         if (mContentView != null) {
194             mContentView.setTextColor(color);
195         }
196     }
197 
198     /**
199      * Sets max lines for the content view.
200      */
setContentMaxLines(int maxLines)201     public void setContentMaxLines(int maxLines) {
202         if (mContentView != null) {
203             mContentView.setMaxLines(maxLines);
204         }
205     }
206 
207     /**
208      * Sets the primary text color.
209      */
setPrimaryTextColor(@olorInt int color)210     public void setPrimaryTextColor(@ColorInt int color) {
211         if (mTitleView != null) {
212             mTitleView.setTextColor(color);
213         }
214     }
215 
216     /**
217      * Sets the text color for the count field.
218      */
setCountTextColor(@olorInt int color)219     public void setCountTextColor(@ColorInt int color) {
220         if (mCountView != null) {
221             mCountView.setTextColor(color);
222         }
223     }
224 
225     /**
226      * Sets the {@link OnClickListener} for the count field.
227      */
setCountOnClickListener(@ullable OnClickListener listener)228     public void setCountOnClickListener(@Nullable OnClickListener listener) {
229         if (mCountView != null) {
230             mCountView.setOnClickListener(listener);
231         }
232     }
233 
234     /**
235      * Sets the text color for the time field.
236      */
setTimeTextColor(@olorInt int color)237     public void setTimeTextColor(@ColorInt int color) {
238         if (mTimeView != null) {
239             mTimeView.setTextColor(color);
240         }
241     }
242 
243     /**
244      * Resets the notification actions empty for recycling.
245      */
reset()246     public void reset() {
247         setVisibility(View.GONE);
248         if (mTitleView != null) {
249             mTitleView.setVisibility(View.GONE);
250         }
251         if (mTitleIconView != null) {
252             mTitleIconView.setVisibility(View.GONE);
253         }
254         if (mContentView != null) {
255             setContentMaxLines(mMaxLines);
256             mContentView.setVisibility(View.GONE);
257         }
258         if (mLargeIconView != null) {
259             mLargeIconView.setVisibility(View.GONE);
260         }
261         setPrimaryTextColor(mDefaultPrimaryTextColor);
262         setSecondaryTextColor(mDefaultSecondaryTextColor);
263         if (mTimeView != null) {
264             mTimeView.setVisibility(View.GONE);
265             mTimeView.setTime(0);
266             setTimeTextColor(mDefaultPrimaryTextColor);
267         }
268 
269         if (mCountView != null) {
270             mCountView.setVisibility(View.GONE);
271             mCountView.setText(null);
272             mCountView.setTextColor(mDefaultPrimaryTextColor);
273         }
274     }
275 
276     @VisibleForTesting
getTitleView()277     TextView getTitleView() {
278         return mTitleView;
279     }
280 
281     @VisibleForTesting
getContentView()282     TextView getContentView() {
283         return mContentView;
284     }
285 
286     @VisibleForTesting
getCountView()287     TextView getCountView() {
288         return mCountView;
289     }
290 
291     @VisibleForTesting
getTimeView()292     DateTimeView getTimeView() {
293         return mTimeView;
294     }
295 }
296