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 
MessagingIntentService()50     public MessagingIntentService() {
51         super("MessagingIntentService");
52     }
53 
54     @Override
onHandleIntent(Intent intent)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      */
handleActionReply(CharSequence replyCharSequence)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      */
getMessage(Intent intent)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      */
recreateBuilderWithMessagingStyle()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 }