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