• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.assist.payloadhandlers;
18 
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.app.RemoteAction;
22 import android.app.RemoteInput;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.graphics.drawable.Drawable;
27 
28 import androidx.annotation.DrawableRes;
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.core.app.NotificationCompat;
32 import androidx.core.app.NotificationCompat.Action;
33 import androidx.core.app.NotificationCompat.Action.SemanticAction;
34 import androidx.core.app.NotificationCompat.MessagingStyle;
35 import androidx.core.graphics.drawable.IconCompat;
36 
37 import com.android.car.messenger.common.Conversation;
38 import com.android.car.messenger.common.Conversation.ConversationAction.ActionType;
39 
40 /**
41  * Util class that provides a conversion between {@link Conversation} and {@link
42  * Notification}
43  */
44 public class ConversationPayloadHandler {
45 
ConversationPayloadHandler()46     private ConversationPayloadHandler() {
47     }
48 
49     /**
50      * Returns the package name from the pending intent
51      */
52     @SuppressWarnings("PendingIntentCreator")
53     @NonNull
getPackageName(@onNull PendingIntent pendingIntent)54     public static String getPackageName(@NonNull PendingIntent pendingIntent) {
55         return pendingIntent.getCreatorPackage();
56     }
57 
58     /**
59      * Creates a notification from {@link Conversation}
60      */
61     @NonNull
createNotificationFromConversation( @onNull Context context, @NonNull String channelId, @NonNull Conversation conversation, @DrawableRes int iconRes, @Nullable String group)62     public static Notification createNotificationFromConversation(
63             @NonNull Context context,
64             @NonNull String channelId,
65             @NonNull Conversation conversation,
66             @DrawableRes int iconRes,
67             @Nullable String group) {
68         MessagingStyle messagingStyle = getMessagingStyle(conversation);
69         Action muteAction = getNotificationAction(context, conversation,
70                 ActionType.ACTION_TYPE_MUTE);
71         Action markAsReadAction = getNotificationAction(context, conversation,
72                 ActionType.ACTION_TYPE_MARK_AS_READ);
73         Action replyAction = getNotificationAction(context, conversation,
74                 ActionType.ACTION_TYPE_REPLY);
75 
76         return new NotificationCompat.Builder(context, channelId)
77                 .setStyle(messagingStyle)
78                 .setSmallIcon(iconRes)
79                 .setLargeIcon(getBitmap(conversation.getConversationIcon(), context))
80                 .addAction(replyAction)
81                 .addAction(markAsReadAction)
82                 .addAction(muteAction)
83                 .setCategory(NotificationCompat.CATEGORY_MESSAGE)
84                 .setGroup(group)
85                 .build();
86     }
87 
88     @Nullable
getNotificationAction( @onNull Context context, @NonNull Conversation conversation, @ActionType int actionType)89     private static Action getNotificationAction(
90             @NonNull Context context,
91             @NonNull Conversation conversation,
92             @ActionType int actionType) {
93         Conversation.ConversationAction conversationAction =
94                 getConversationAction(conversation, actionType);
95         Action notificationAction = null;
96         if (conversationAction != null) {
97             RemoteAction remoteAction = conversationAction.getRemoteAction();
98             IconCompat icon =
99                     IconCompat.createFromIcon(context, remoteAction.getIcon());
100             Action.Builder builder =
101                     new Action.Builder(
102                             /* icon= */ icon,
103                             /* charSequence= */ remoteAction.getTitle(),
104                             remoteAction.getActionIntent())
105                             .setShowsUserInterface(false)
106                             .setSemanticAction(getSemanticAction(actionType));
107             if (conversationAction.getRemoteInput() != null) {
108                 builder.addRemoteInput(toCompat(conversationAction.getRemoteInput()));
109             }
110             notificationAction = builder.build();
111         }
112         return notificationAction;
113     }
114 
115     @SemanticAction
getSemanticAction(@ctionType int actionType)116     private static int getSemanticAction(@ActionType int actionType) {
117         switch (actionType) {
118             case ActionType.ACTION_TYPE_MUTE:
119                 return Action.SEMANTIC_ACTION_MUTE;
120             case ActionType.ACTION_TYPE_MARK_AS_READ:
121                 return Action.SEMANTIC_ACTION_MARK_AS_READ;
122             case ActionType.ACTION_TYPE_REPLY:
123                 return Action.SEMANTIC_ACTION_REPLY;
124             default:
125                 return Action.SEMANTIC_ACTION_NONE;
126         }
127     }
128 
getMessagingStyle( @onNull Conversation conversation)129     private static MessagingStyle getMessagingStyle(
130             @NonNull Conversation conversation) {
131         MessagingStyle messagingStyle =
132                 new MessagingStyle(conversation.getUser());
133         messagingStyle.setGroupConversation(isGroupConversation(conversation));
134         messagingStyle.setConversationTitle(conversation.getConversationTitle());
135         for (Conversation.Message message : conversation.getMessages()) {
136             messagingStyle.addMessage(
137                     new MessagingStyle.Message(
138                             message.getText(), message.getTimestamp(), message.getPerson()));
139         }
140         return messagingStyle;
141     }
142 
143     @Nullable
getBitmap(@ullable IconCompat icon, Context context)144     private static Bitmap getBitmap(@Nullable IconCompat icon, Context context) {
145         if (icon == null) {
146             return null;
147         }
148         Drawable drawable = icon.loadDrawable(context);
149         if (drawable instanceof BitmapDrawable) {
150             return ((BitmapDrawable) drawable).getBitmap();
151         } else {
152             return null;
153         }
154     }
155 
isGroupConversation(Conversation conversation)156     private static boolean isGroupConversation(Conversation conversation) {
157         if (conversation.getParticipants().contains(conversation.getUser())) {
158             return conversation.getParticipants().size() > 2;
159         } else {
160             return conversation.getParticipants().size() > 1;
161         }
162     }
163 
164     @Nullable
getConversationAction( Conversation conversation, @ActionType int action)165     private static Conversation.ConversationAction getConversationAction(
166             Conversation conversation, @ActionType int action) {
167         if (conversation.getActions() == null) {
168             return null;
169         }
170         return conversation.getActions().stream()
171                 .filter(it -> it.getActionType() == action)
172                 .findFirst()
173                 .orElse(null);
174     }
175 
176     // Conversation classes use android.app.RemoteInput
177     // instead of androidx.core.app.RemoteInput in order to support
178     // being parcelable. While Notification uses androidx.core.app.RemoteInput.
179     // We used these methods to convert between the two.
toCompat(RemoteInput src)180     private static androidx.core.app.RemoteInput toCompat(RemoteInput src) {
181         androidx.core.app.RemoteInput.Builder builder =
182                 new androidx.core.app.RemoteInput.Builder(src.getResultKey())
183                         .setLabel(src.getLabel())
184                         .addExtras(src.getExtras());
185         return builder.build();
186     }
187 }
188