1 /*
2  * Copyright (C) 2014 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.example.android.messagingservice;
18 
19 import android.app.PendingIntent;
20 import android.app.Service;
21 import android.content.Intent;
22 import android.graphics.BitmapFactory;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.Message;
26 import android.os.Messenger;
27 import android.support.v4.app.NotificationCompat.CarExtender;
28 import android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation;
29 import android.support.v4.app.NotificationCompat;
30 import android.support.v4.app.NotificationManagerCompat;
31 import android.support.v4.app.RemoteInput;
32 import android.util.Log;
33 
34 import java.lang.ref.WeakReference;
35 import java.util.Iterator;
36 
37 public class MessagingService extends Service {
38     private static final String TAG = MessagingService.class.getSimpleName();
39     private static final String EOL = "\n";
40     private static final String READ_ACTION =
41             "com.example.android.messagingservice.ACTION_MESSAGE_READ";
42     public static final String REPLY_ACTION =
43             "com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
44     public static final String CONVERSATION_ID = "conversation_id";
45     public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
46     public static final int MSG_SEND_NOTIFICATION = 1;
47 
48     private NotificationManagerCompat mNotificationManager;
49 
50     private final Messenger mMessenger = new Messenger(new IncomingHandler(this));
51 
52     @Override
onCreate()53     public void onCreate() {
54         Log.d(TAG, "onCreate");
55         mNotificationManager = NotificationManagerCompat.from(getApplicationContext());
56     }
57 
58     @Override
onBind(Intent intent)59     public IBinder onBind(Intent intent) {
60         Log.d(TAG, "onBind");
61         return mMessenger.getBinder();
62     }
63 
64     // Creates an intent that will be triggered when a message is marked as read.
getMessageReadIntent(int id)65     private Intent getMessageReadIntent(int id) {
66         return new Intent()
67                 .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
68                 .setAction(READ_ACTION)
69                 .putExtra(CONVERSATION_ID, id);
70     }
71 
72     // Creates an Intent that will be triggered when a voice reply is received.
getMessageReplyIntent(int conversationId)73     private Intent getMessageReplyIntent(int conversationId) {
74         return new Intent()
75                 .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
76                 .setAction(REPLY_ACTION)
77                 .putExtra(CONVERSATION_ID, conversationId);
78     }
79 
sendNotification(int howManyConversations, int messagesPerConversation)80     private void sendNotification(int howManyConversations, int messagesPerConversation) {
81         Conversations.Conversation[] conversations = Conversations.getUnreadConversations(
82                 howManyConversations, messagesPerConversation);
83         for (Conversations.Conversation conv : conversations) {
84             sendNotificationForConversation(conv);
85         }
86     }
87 
sendNotificationForConversation(Conversations.Conversation conversation)88     private void sendNotificationForConversation(Conversations.Conversation conversation) {
89         // A pending Intent for reads
90         PendingIntent readPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
91                 conversation.getConversationId(),
92                 getMessageReadIntent(conversation.getConversationId()),
93                 PendingIntent.FLAG_UPDATE_CURRENT);
94 
95         // Build a RemoteInput for receiving voice input in a Car Notification or text input on
96         // devices that support text input (like devices on Android N and above).
97         RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REMOTE_REPLY)
98                 .setLabel(getString(R.string.reply))
99                 .build();
100 
101         // Building a Pending Intent for the reply action to trigger
102         PendingIntent replyIntent = PendingIntent.getBroadcast(getApplicationContext(),
103                 conversation.getConversationId(),
104                 getMessageReplyIntent(conversation.getConversationId()),
105                 PendingIntent.FLAG_UPDATE_CURRENT);
106 
107         // Build an Android N compatible Remote Input enabled action.
108         NotificationCompat.Action actionReplyByRemoteInput = new NotificationCompat.Action.Builder(
109                 R.drawable.notification_icon, getString(R.string.reply), replyIntent)
110                 .addRemoteInput(remoteInput)
111                 .build();
112 
113         // Create the UnreadConversation and populate it with the participant name,
114         // read and reply intents.
115         UnreadConversation.Builder unreadConvBuilder =
116                 new UnreadConversation.Builder(conversation.getParticipantName())
117                 .setLatestTimestamp(conversation.getTimestamp())
118                 .setReadPendingIntent(readPendingIntent)
119                 .setReplyAction(replyIntent, remoteInput);
120 
121         // Note: Add messages from oldest to newest to the UnreadConversation.Builder
122         StringBuilder messageForNotification = new StringBuilder();
123         for (Iterator<String> messages = conversation.getMessages().iterator();
124              messages.hasNext(); ) {
125             String message = messages.next();
126             unreadConvBuilder.addMessage(message);
127             messageForNotification.append(message);
128             if (messages.hasNext()) {
129                 messageForNotification.append(EOL);
130             }
131         }
132 
133         NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext())
134                 .setSmallIcon(R.drawable.notification_icon)
135                 .setLargeIcon(BitmapFactory.decodeResource(
136                         getApplicationContext().getResources(), R.drawable.android_contact))
137                 .setContentText(messageForNotification.toString())
138                 .setWhen(conversation.getTimestamp())
139                 .setContentTitle(conversation.getParticipantName())
140                 .setContentIntent(readPendingIntent)
141                 .extend(new CarExtender()
142                         .setUnreadConversation(unreadConvBuilder.build())
143                         .setColor(getApplicationContext().getResources()
144                                 .getColor(R.color.default_color_light)))
145                 .addAction(actionReplyByRemoteInput);
146 
147         MessageLogger.logMessage(getApplicationContext(), "Sending notification "
148                 + conversation.getConversationId() + " conversation: " + conversation);
149 
150         mNotificationManager.notify(conversation.getConversationId(), builder.build());
151     }
152 
153     /**
154      * Handler for incoming messages from clients.
155      */
156     private static class IncomingHandler extends Handler {
157         private final WeakReference<MessagingService> mReference;
158 
IncomingHandler(MessagingService service)159         IncomingHandler(MessagingService service) {
160             mReference = new WeakReference<>(service);
161         }
162 
163         @Override
handleMessage(Message msg)164         public void handleMessage(Message msg) {
165             MessagingService service = mReference.get();
166             switch (msg.what) {
167                 case MSG_SEND_NOTIFICATION:
168                     int howManyConversations = msg.arg1 <= 0 ? 1 : msg.arg1;
169                     int messagesPerConversation = msg.arg2 <= 0 ? 1 : msg.arg2;
170                     if (service != null) {
171                         service.sendNotification(howManyConversations, messagesPerConversation);
172                     }
173                     break;
174                 default:
175                     super.handleMessage(msg);
176             }
177         }
178     }
179 }
180