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