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