1 /*
2 Copyright 2016 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.example.android.wearable.wear.wearnotifications.handlers;
17 
18 import android.app.IntentService;
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.content.Intent;
22 import android.graphics.BitmapFactory;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.support.v4.app.NotificationCompat.MessagingStyle;
26 import android.support.v4.app.NotificationManagerCompat;
27 import android.support.v4.app.RemoteInput;
28 import android.support.v4.app.TaskStackBuilder;
29 import android.support.v7.app.NotificationCompat;
30 import android.util.Log;
31 
32 import com.example.android.wearable.wear.wearnotifications.GlobalNotificationBuilder;
33 import com.example.android.wearable.wear.wearnotifications.MainActivity;
34 import com.example.android.wearable.wear.wearnotifications.R;
35 import com.example.android.wearable.wear.wearnotifications.mock.MockDatabase;
36 
37 /**
38  * Asynchronously handles updating messaging app posts (and active Notification) with replies from
39  * user in a conversation. Notification for social app use MessagingStyle.
40  */
41 public class MessagingIntentService extends IntentService {
42 
43     private static final String TAG = "MessagingIntentService";
44 
45     public static final String ACTION_REPLY =
46             "com.example.android.wearable.wear.wearnotifications.handlers.action.REPLY";
47 
48     public static final String EXTRA_REPLY =
49             "com.example.android.wearable.wear.wearnotifications.handlers.extra.REPLY";
50 
51 
MessagingIntentService()52     public MessagingIntentService() {
53         super("MessagingIntentService");
54     }
55 
56     @Override
onHandleIntent(Intent intent)57     protected void onHandleIntent(Intent intent) {
58         Log.d(TAG, "onHandleIntent(): " + intent);
59 
60         if (intent != null) {
61             final String action = intent.getAction();
62             if (ACTION_REPLY.equals(action)) {
63                 handleActionReply(getMessage(intent));
64             }
65         }
66     }
67 
68     /**
69      * Handles action for replying to messages from the notification.
70      */
handleActionReply(CharSequence replyCharSequence)71     private void handleActionReply(CharSequence replyCharSequence) {
72         Log.d(TAG, "handleActionReply(): " + replyCharSequence);
73 
74         if (replyCharSequence != null) {
75 
76             // TODO: Asynchronously save your message to Database and servers.
77 
78             /*
79              * You have two options for updating your notification (this class uses approach #2):
80              *
81              *  1. Use a new NotificationCompatBuilder to create the Notification. This approach
82              *  requires you to get *ALL* the information that existed in the previous
83              *  Notification (and updates) and pass it to the builder. This is the approach used in
84              *  the MainActivity.
85              *
86              *  2. Use the original NotificationCompatBuilder to create the Notification. This
87              *  approach requires you to store a reference to the original builder. The benefit is
88              *  you only need the new/updated information. In our case, the reply from the user
89              *  which we already have here.
90              *
91              *  IMPORTANT NOTE: You shouldn't save/modify the resulting Notification object using
92              *  its member variables and/or legacy APIs. If you want to retain anything from update
93              *  to update, retain the Builder as option 2 outlines.
94              */
95 
96             // Retrieves NotificationCompat.Builder used to create initial Notification
97             NotificationCompat.Builder notificationCompatBuilder =
98                     GlobalNotificationBuilder.getNotificationCompatBuilderInstance();
99 
100             // Recreate builder from persistent state if app process is killed
101             if (notificationCompatBuilder == null) {
102                 // Note: New builder set globally in the method
103                 notificationCompatBuilder = recreateBuilderWithMessagingStyle();
104             }
105 
106 
107             // Since we are adding to the MessagingStyle, we need to first retrieve the
108             // current MessagingStyle from the Notification itself.
109             Notification notification = notificationCompatBuilder.build();
110             MessagingStyle messagingStyle =
111                     NotificationCompat.MessagingStyle
112                             .extractMessagingStyleFromNotification(notification);
113 
114             // Add new message to the MessagingStyle
115             messagingStyle.addMessage(replyCharSequence, System.currentTimeMillis(), null);
116 
117             // Updates the Notification
118             notification = notificationCompatBuilder
119                     .setStyle(messagingStyle)
120                     .build();
121 
122             // Pushes out the updated Notification
123             NotificationManagerCompat notificationManagerCompat =
124                     NotificationManagerCompat.from(getApplicationContext());
125             notificationManagerCompat.notify(MainActivity.NOTIFICATION_ID, notification);
126         }
127     }
128 
129     /*
130      * Extracts CharSequence created from the RemoteInput associated with the Notification.
131      */
getMessage(Intent intent)132     private CharSequence getMessage(Intent intent) {
133         Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
134         if (remoteInput != null) {
135             return remoteInput.getCharSequence(EXTRA_REPLY);
136         }
137         return null;
138     }
139 
140     /*
141      * This recreates the notification from the persistent state in case the app process was killed.
142      * It is basically the same code for creating the Notification from MainActivity.
143      */
recreateBuilderWithMessagingStyle()144     private NotificationCompat.Builder recreateBuilderWithMessagingStyle() {
145 
146         // Main steps for building a MESSAGING_STYLE notification (for more detailed comments on
147         // building this notification, check MainActivity.java)::
148         //      0. Get your data
149         //      1. Build the MESSAGING_STYLE
150         //      2. Add support for Wear 1.+
151         //      3. Set up main Intent for notification
152         //      4. Set up RemoteInput (users can input directly from notification)
153         //      5. Build and issue the notification
154 
155         // 0. Get your data
156         MockDatabase.MessagingStyleCommsAppData messagingData =
157                 MockDatabase.getMessagingStyleData();
158 
159         // 1. Build the Notification.Style (MESSAGING_STYLE)
160         String contentTitle = messagingData.getContentTitle();
161 
162         MessagingStyle messagingStyle =
163                 new NotificationCompat.MessagingStyle(messagingData.getReplayName())
164                         .setConversationTitle(contentTitle);
165 
166         // Adds all Messages
167         // Note: Messages include the text, timestamp, and sender
168         for (MessagingStyle.Message message : messagingData.getMessages()) {
169             messagingStyle.addMessage(message);
170         }
171 
172 
173         // 2. Add support for Wear 1.+
174         String fullMessageForWearVersion1 = messagingData.getFullConversation();
175 
176         Notification chatHistoryForWearV1 = new NotificationCompat.Builder(getApplicationContext())
177                 .setStyle(new NotificationCompat.BigTextStyle().bigText(fullMessageForWearVersion1))
178                 .setContentTitle(contentTitle)
179                 .setSmallIcon(R.drawable.ic_launcher)
180                 .setContentText(fullMessageForWearVersion1)
181                 .build();
182 
183         NotificationCompat.WearableExtender wearableExtenderForWearVersion1 =
184                 new NotificationCompat.WearableExtender()
185                         .addPage(chatHistoryForWearV1);
186 
187 
188 
189         // 3. Set up main Intent for notification
190         Intent notifyIntent = new Intent(this, MessagingMainActivity.class);
191 
192         TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
193         stackBuilder.addParentStack(MessagingMainActivity.class);
194         stackBuilder.addNextIntent(notifyIntent);
195 
196         PendingIntent mainPendingIntent =
197                 PendingIntent.getActivity(
198                         this,
199                         0,
200                         notifyIntent,
201                         PendingIntent.FLAG_UPDATE_CURRENT
202                 );
203 
204 
205         // 4. Set up RemoteInput, so users can input (keyboard and voice) from notification
206         String replyLabel = getString(R.string.reply_label);
207         RemoteInput remoteInput = new RemoteInput.Builder(MessagingIntentService.EXTRA_REPLY)
208                 .setLabel(replyLabel)
209                 .build();
210 
211         PendingIntent replyActionPendingIntent;
212 
213         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
214             Intent intent = new Intent(this, MessagingIntentService.class);
215             intent.setAction(MessagingIntentService.ACTION_REPLY);
216             replyActionPendingIntent = PendingIntent.getService(this, 0, intent, 0);
217 
218         } else {
219             replyActionPendingIntent = mainPendingIntent;
220         }
221 
222         NotificationCompat.Action replyAction =
223                 new NotificationCompat.Action.Builder(
224                         R.drawable.ic_reply_white_18dp,
225                         replyLabel,
226                         replyActionPendingIntent)
227                         .addRemoteInput(remoteInput)
228                         // Allows system to generate replies by context of conversation
229                         .setAllowGeneratedReplies(true)
230                         .build();
231 
232 
233         // 5. Build and issue the notification
234         NotificationCompat.Builder notificationCompatBuilder =
235                 new NotificationCompat.Builder(getApplicationContext());
236 
237         GlobalNotificationBuilder.setNotificationCompatBuilderInstance(notificationCompatBuilder);
238 
239         notificationCompatBuilder
240                 .setStyle(messagingStyle)
241                 .setContentTitle(contentTitle)
242                 .setContentText(messagingData.getContentText())
243                 .setSmallIcon(R.drawable.ic_launcher)
244                 .setLargeIcon(BitmapFactory.decodeResource(
245                         getResources(),
246                         R.drawable.ic_person_black_48dp))
247                 .setContentIntent(mainPendingIntent)
248                 .setColor(getResources().getColor(R.color.colorPrimary))
249                 .setSubText(Integer.toString(messagingData.getNumberOfNewMessages()))
250                 .addAction(replyAction)
251                 .setCategory(Notification.CATEGORY_MESSAGE)
252                 .setPriority(Notification.PRIORITY_HIGH)
253                 .setVisibility(Notification.VISIBILITY_PRIVATE)
254                 .extend(wearableExtenderForWearVersion1);
255 
256         for (String name : messagingData.getParticipants()) {
257             notificationCompatBuilder.addPerson(name);
258         }
259 
260         return notificationCompatBuilder;
261     }
262 }