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.service.notification.StatusBarNotification;
26 import android.text.TextUtils;
27 import android.view.View;
28 import android.widget.DateTimeView;
29 import android.widget.ImageButton;
30 import android.widget.TextView;
31 
32 import androidx.core.app.NotificationCompat.MessagingStyle;
33 
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 StatusBarNotification} to a messaging car notification template without
79      * UX restriction.
80      */
81     @Override
bind(StatusBarNotification statusBarNotification, boolean isInGroup, boolean isHeadsUp)82     public void bind(StatusBarNotification statusBarNotification, boolean isInGroup,
83             boolean isHeadsUp) {
84         super.bind(statusBarNotification, isInGroup, isHeadsUp);
85         bindBody(statusBarNotification, isInGroup, /* isRestricted= */ false, isHeadsUp);
86         mHeaderView.bind(statusBarNotification, isInGroup);
87         mActionsView.bind(mClickHandlerFactory, statusBarNotification);
88     }
89 
90     /**
91      * Binds a {@link StatusBarNotification} to a messaging car notification template with
92      * UX restriction.
93      */
bindRestricted(StatusBarNotification statusBarNotification, boolean isInGroup, boolean isHeadsUp)94     public void bindRestricted(StatusBarNotification statusBarNotification, boolean isInGroup,
95             boolean isHeadsUp) {
96         super.bind(statusBarNotification, isInGroup, isHeadsUp);
97         bindBody(statusBarNotification, isInGroup, /* isRestricted= */ true, isHeadsUp);
98         mHeaderView.bind(statusBarNotification, isInGroup);
99         mActionsView.bind(mClickHandlerFactory, statusBarNotification);
100     }
101 
102     /**
103      * Private method that binds the data to the view.
104      */
bindBody( StatusBarNotification statusBarNotification, boolean isInGroup, boolean isRestricted, boolean isHeadsUp)105     private void bindBody(
106             StatusBarNotification statusBarNotification, boolean isInGroup, boolean isRestricted,
107             boolean isHeadsUp) {
108 
109         Notification notification = statusBarNotification.getNotification();
110         CharSequence messageText = null;
111         CharSequence conversationTitle = null;
112         Icon avatar = null;
113         Integer messageCount = null;
114 
115         final MessagingStyle messagingStyle =
116                 MessagingStyle.extractMessagingStyleFromNotification(notification);
117         if (messagingStyle != null) conversationTitle = messagingStyle.getConversationTitle();
118 
119         Bundle extras = notification.extras;
120         Parcelable[] messagesData = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
121         if (messagesData != null) {
122             List<Notification.MessagingStyle.Message> messages =
123                     Notification.MessagingStyle.Message.getMessagesFromBundleArray(messagesData);
124             if (messages != null && !messages.isEmpty()) {
125                 messageCount = messages.size();
126                 // Use the latest message
127                 Notification.MessagingStyle.Message message = messages.get(messages.size() - 1);
128                 messageText = message.getText();
129                 Person sender = message.getSenderPerson();
130                 if (sender != null) {
131                     avatar = sender.getIcon();
132                 }
133                 if (conversationTitle == null) {
134                     conversationTitle = sender != null ? sender.getName() : message.getSender();
135                 }
136             }
137         }
138 
139         // app did not use messaging style, fall back to standard fields
140         if (messageCount == null) {
141             messageCount = notification.number;
142             if (messageCount == 0) {
143                 messageCount = 1; // a notification should at least represent 1 message
144             }
145         }
146 
147         if (TextUtils.isEmpty(conversationTitle)) {
148             conversationTitle = extras.getCharSequence(Notification.EXTRA_TITLE);
149         }
150         if (isRestricted) {
151             messageText = mContext.getResources().getQuantityString(
152                     R.plurals.restricted_message_text, messageCount, messageCount);
153         } else if (TextUtils.isEmpty(messageText)) {
154             messageText = extras.getCharSequence(Notification.EXTRA_TEXT);
155         }
156 
157         if (avatar == null) {
158             avatar = notification.getLargeIcon();
159         }
160 
161         if (!TextUtils.isEmpty(conversationTitle)) {
162             mTitleView.setVisibility(View.VISIBLE);
163             mTitleView.setText(conversationTitle);
164         }
165 
166         if (isInGroup && notification.showsTime()) {
167             mTimeView.setVisibility(View.VISIBLE);
168             mTimeView.setTime(notification.when);
169         }
170 
171         if (!TextUtils.isEmpty(messageText)) {
172             messageText = PreprocessingManager.getInstance(mContext).trimText(messageText);
173             mMessageView.setVisibility(View.VISIBLE);
174             mMessageView.setText(messageText);
175         }
176 
177         if (avatar != null) {
178             mAvatarView.setVisibility(View.VISIBLE);
179             mAvatarView.setImageIcon(avatar);
180         }
181 
182         int unshownCount = messageCount - 1;
183         if (!isRestricted && unshownCount > 0) {
184             String unshownCountText =
185                     mContext.getString(R.string.message_unshown_count, unshownCount);
186             mUnshownCountView.setVisibility(View.VISIBLE);
187             mUnshownCountView.setText(unshownCountText);
188             mUnshownCountView.setTextColor(getAccentColor());
189         }
190 
191         if (isHeadsUp) {
192             mBodyView.bindTitleAndMessage(conversationTitle, messageText);
193         }
194     }
195 
196     @Override
reset()197     void reset() {
198         super.reset();
199         mTitleView.setVisibility(View.GONE);
200         mTitleView.setText(null);
201         if (mTimeView != null) {
202             mTimeView.setVisibility(View.GONE);
203         }
204 
205         mMessageView.setVisibility(View.GONE);
206         mMessageView.setText(null);
207 
208         mAvatarView.setVisibility(View.GONE);
209         mAvatarView.setImageIcon(null);
210 
211         mUnshownCountView.setVisibility(View.GONE);
212         mUnshownCountView.setText(null);
213         mUnshownCountView.setTextColor(mDefaultPrimaryForegroundColor);
214     }
215 }
216