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