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 package com.android.car.notification.template;
17 
18 import android.annotation.ColorInt;
19 import android.app.Notification;
20 import android.app.Person;
21 import android.content.Context;
22 import android.graphics.drawable.Icon;
23 import android.os.Bundle;
24 import android.os.Parcelable;
25 import android.text.TextUtils;
26 import android.view.View;
27 import android.widget.DateTimeView;
28 import android.widget.ImageButton;
29 import android.widget.TextView;
30 
31 import androidx.core.app.NotificationCompat.MessagingStyle;
32 
33 import com.android.car.notification.AlertEntry;
34 import com.android.car.notification.NotificationClickHandlerFactory;
35 import com.android.car.notification.PreprocessingManager;
36 import com.android.car.notification.R;
37 
38 import java.util.List;
39 
40 /**
41  * Messaging notification template that displays a messaging notification and a voice reply button.
42  */
43 public class MessageNotificationViewHolder extends CarNotificationBaseViewHolder {
44     @ColorInt
45     private final int mDefaultPrimaryForegroundColor;
46     private final Context mContext;
47     private final CarNotificationBodyView mBodyView;
48     private final CarNotificationHeaderView mHeaderView;
49     private final CarNotificationActionsView mActionsView;
50     private final TextView mTitleView;
51     private final DateTimeView mTimeView;
52     private final TextView mMessageView;
53     private final TextView mUnshownCountView;
54     private final ImageButton mAvatarView;
55     private NotificationClickHandlerFactory mClickHandlerFactory;
56 
MessageNotificationViewHolder( View view, NotificationClickHandlerFactory clickHandlerFactory)57     public MessageNotificationViewHolder(
58             View view, NotificationClickHandlerFactory clickHandlerFactory) {
59         super(view, clickHandlerFactory);
60         mContext = view.getContext();
61         mDefaultPrimaryForegroundColor = mContext.getColor(R.color.primary_text_color);
62         mHeaderView = view.findViewById(R.id.notification_header);
63         mActionsView = view.findViewById(R.id.notification_actions);
64         mTitleView = view.findViewById(R.id.notification_body_title);
65         mTimeView = view.findViewById(R.id.in_group_time_stamp);
66         if (mTimeView != null) {
67             // HUN template does not include the time stamp.
68             mTimeView.setShowRelativeTime(true);
69         }
70         mMessageView = view.findViewById(R.id.notification_body_content);
71         mBodyView = view.findViewById(R.id.notification_body);
72         mUnshownCountView = view.findViewById(R.id.message_count);
73         mAvatarView = view.findViewById(R.id.notification_body_icon);
74         mClickHandlerFactory = clickHandlerFactory;
75     }
76 
77     /**
78      * Binds a {@link AlertEntry} to a messaging car notification template without
79      * UX restriction.
80      */
81     @Override
bind(AlertEntry alertEntry, boolean isInGroup, boolean isHeadsUp)82     public void bind(AlertEntry alertEntry, boolean isInGroup,
83             boolean isHeadsUp) {
84         super.bind(alertEntry, isInGroup, isHeadsUp);
85         bindBody(alertEntry, isInGroup, /* isRestricted= */ false, isHeadsUp);
86         mHeaderView.bind(alertEntry, isInGroup);
87         mActionsView.bind(mClickHandlerFactory, alertEntry);
88     }
89 
90     /**
91      * Binds a {@link AlertEntry} to a messaging car notification template with
92      * UX restriction.
93      */
bindRestricted(AlertEntry alertEntry, boolean isInGroup, boolean isHeadsUp)94     public void bindRestricted(AlertEntry alertEntry, boolean isInGroup, boolean isHeadsUp) {
95         super.bind(alertEntry, isInGroup, isHeadsUp);
96         bindBody(alertEntry, isInGroup, /* isRestricted= */ true, isHeadsUp);
97         mHeaderView.bind(alertEntry, isInGroup);
98         mActionsView.bind(mClickHandlerFactory, alertEntry);
99     }
100 
101     /**
102      * Private method that binds the data to the view.
103      */
bindBody(AlertEntry alertEntry, boolean isInGroup, boolean isRestricted, boolean isHeadsUp)104     private void bindBody(AlertEntry alertEntry, boolean isInGroup, boolean isRestricted,
105             boolean isHeadsUp) {
106 
107         Notification notification = alertEntry.getNotification();
108         CharSequence messageText = null;
109         CharSequence conversationTitle = null;
110         Icon avatar = null;
111         Integer messageCount = null;
112 
113         final MessagingStyle messagingStyle =
114                 MessagingStyle.extractMessagingStyleFromNotification(notification);
115         if (messagingStyle != null) conversationTitle = messagingStyle.getConversationTitle();
116 
117         Bundle extras = notification.extras;
118         Parcelable[] messagesData = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
119         if (messagesData != null) {
120             List<Notification.MessagingStyle.Message> messages =
121                     Notification.MessagingStyle.Message.getMessagesFromBundleArray(messagesData);
122             if (messages != null && !messages.isEmpty()) {
123                 messageCount = messages.size();
124                 // Use the latest message
125                 Notification.MessagingStyle.Message message = messages.get(messages.size() - 1);
126                 messageText = message.getText();
127                 Person sender = message.getSenderPerson();
128                 if (sender != null) {
129                     avatar = sender.getIcon();
130                 }
131                 if (conversationTitle == null) {
132                     conversationTitle = sender != null ? sender.getName() : message.getSender();
133                 }
134             }
135         }
136 
137         // app did not use messaging style, fall back to standard fields
138         if (messageCount == null) {
139             messageCount = notification.number;
140             if (messageCount == 0) {
141                 messageCount = 1; // a notification should at least represent 1 message
142             }
143         }
144 
145         if (TextUtils.isEmpty(conversationTitle)) {
146             conversationTitle = extras.getCharSequence(Notification.EXTRA_TITLE);
147         }
148         if (isRestricted) {
149             messageText = mContext.getResources().getQuantityString(
150                     R.plurals.restricted_message_text, messageCount, messageCount);
151         } else if (TextUtils.isEmpty(messageText)) {
152             messageText = extras.getCharSequence(Notification.EXTRA_TEXT);
153         }
154 
155         if (avatar == null) {
156             avatar = notification.getLargeIcon();
157         }
158 
159         if (!TextUtils.isEmpty(conversationTitle)) {
160             mTitleView.setVisibility(View.VISIBLE);
161             mTitleView.setText(conversationTitle);
162         }
163 
164         if (isInGroup && notification.showsTime()) {
165             mTimeView.setVisibility(View.VISIBLE);
166             mTimeView.setTime(notification.when);
167         }
168 
169         if (!TextUtils.isEmpty(messageText)) {
170             messageText = PreprocessingManager.getInstance(mContext).trimText(messageText);
171             mMessageView.setVisibility(View.VISIBLE);
172             mMessageView.setText(messageText);
173         }
174 
175         if (avatar != null) {
176             mAvatarView.setVisibility(View.VISIBLE);
177             mAvatarView.setImageIcon(avatar);
178         }
179 
180         int unshownCount = messageCount - 1;
181         if (!isRestricted && unshownCount > 0) {
182             String unshownCountText =
183                     mContext.getString(R.string.message_unshown_count, unshownCount);
184             mUnshownCountView.setVisibility(View.VISIBLE);
185             mUnshownCountView.setText(unshownCountText);
186             mUnshownCountView.setTextColor(getAccentColor());
187         }
188 
189         if (isHeadsUp) {
190             mBodyView.bindTitleAndMessage(conversationTitle, messageText);
191         }
192     }
193 
194     @Override
reset()195     void reset() {
196         super.reset();
197         mTitleView.setVisibility(View.GONE);
198         mTitleView.setText(null);
199         if (mTimeView != null) {
200             mTimeView.setVisibility(View.GONE);
201         }
202 
203         mMessageView.setVisibility(View.GONE);
204         mMessageView.setText(null);
205 
206         mAvatarView.setVisibility(View.GONE);
207         mAvatarView.setImageIcon(null);
208 
209         mUnshownCountView.setVisibility(View.GONE);
210         mUnshownCountView.setText(null);
211         mUnshownCountView.setTextColor(mDefaultPrimaryForegroundColor);
212     }
213 }
214