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