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 }