| /* |
| Copyright 2016 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| package com.example.android.wearable.wear.wearnotifications.handlers; |
| |
| import android.app.IntentService; |
| import android.app.Notification; |
| import android.app.PendingIntent; |
| import android.content.Intent; |
| import android.graphics.BitmapFactory; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.util.Log; |
| |
| import androidx.core.app.NotificationCompat; |
| import androidx.core.app.NotificationCompat.Action; |
| import androidx.core.app.NotificationCompat.MessagingStyle; |
| import androidx.core.app.NotificationManagerCompat; |
| import androidx.core.app.Person; |
| import androidx.core.app.RemoteInput; |
| import androidx.core.app.TaskStackBuilder; |
| import androidx.core.content.ContextCompat; |
| |
| import com.example.android.wearable.wear.common.mock.MockDatabase; |
| import com.example.android.wearable.wear.common.util.NotificationUtil; |
| import com.example.android.wearable.wear.wearnotifications.GlobalNotificationBuilder; |
| import com.example.android.wearable.wear.wearnotifications.MainActivity; |
| import com.example.android.wearable.wear.wearnotifications.R; |
| |
| /** |
| * Asynchronously handles updating messaging app posts (and active Notification) with replies from |
| * user in a conversation. Notification for social app use MessagingStyle. |
| */ |
| public class MessagingIntentService extends IntentService { |
| |
| private static final String TAG = "MessagingIntentService"; |
| |
| public static final String ACTION_REPLY = |
| "com.example.android.wearable.wear.wearnotifications.handlers.action.REPLY"; |
| |
| public static final String EXTRA_REPLY = |
| "com.example.android.wearable.wear.wearnotifications.handlers.extra.REPLY"; |
| |
| public MessagingIntentService() { |
| super("MessagingIntentService"); |
| } |
| |
| @Override |
| protected void onHandleIntent(Intent intent) { |
| Log.d(TAG, "onHandleIntent(): " + intent); |
| |
| if (intent != null) { |
| final String action = intent.getAction(); |
| if (ACTION_REPLY.equals(action)) { |
| handleActionReply(getMessage(intent)); |
| } |
| } |
| } |
| |
| /** Handles action for replying to messages from the notification. */ |
| private void handleActionReply(CharSequence replyCharSequence) { |
| Log.d(TAG, "handleActionReply(): " + replyCharSequence); |
| |
| if (replyCharSequence != null) { |
| |
| // TODO: Asynchronously save your message to Database and servers. |
| |
| /* |
| * You have two options for updating your notification (this class uses approach #2): |
| * |
| * 1. Use a new NotificationCompatBuilder to create the Notification. This approach |
| * requires you to get *ALL* the information that existed in the previous |
| * Notification (and updates) and pass it to the builder. This is the approach used in |
| * the MainActivity. |
| * |
| * 2. Use the original NotificationCompatBuilder to create the Notification. This |
| * approach requires you to store a reference to the original builder. The benefit is |
| * you only need the new/updated information. In our case, the reply from the user |
| * which we already have here. |
| * |
| * IMPORTANT NOTE: You shouldn't save/modify the resulting Notification object using |
| * its member variables and/or legacy APIs. If you want to retain anything from update |
| * to update, retain the Builder as option 2 outlines. |
| */ |
| |
| // Retrieves NotificationCompat.Builder used to create initial Notification |
| NotificationCompat.Builder notificationCompatBuilder = |
| GlobalNotificationBuilder.getNotificationCompatBuilderInstance(); |
| |
| // Recreate builder from persistent state if app process is killed |
| if (notificationCompatBuilder == null) { |
| // Note: New builder set globally in the method |
| notificationCompatBuilder = recreateBuilderWithMessagingStyle(); |
| } |
| |
| // Since we are adding to the MessagingStyle, we need to first retrieve the |
| // current MessagingStyle from the Notification itself. |
| Notification notification = notificationCompatBuilder.build(); |
| MessagingStyle messagingStyle = |
| NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification( |
| notification); |
| |
| // Add new message to the MessagingStyle. Set last parameter to null for responses |
| // from user. |
| messagingStyle.addMessage(replyCharSequence, System.currentTimeMillis(), (Person) null); |
| |
| // Updates the Notification |
| notification = notificationCompatBuilder.setStyle(messagingStyle).build(); |
| |
| // Pushes out the updated Notification |
| NotificationManagerCompat notificationManagerCompat = |
| NotificationManagerCompat.from(getApplicationContext()); |
| notificationManagerCompat.notify(MainActivity.NOTIFICATION_ID, notification); |
| } |
| } |
| |
| /* |
| * Extracts CharSequence created from the RemoteInput associated with the Notification. |
| */ |
| private CharSequence getMessage(Intent intent) { |
| Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); |
| if (remoteInput != null) { |
| return remoteInput.getCharSequence(EXTRA_REPLY); |
| } |
| return null; |
| } |
| |
| /* |
| * This recreates the notification from the persistent state in case the app process was killed. |
| * It is basically the same code for creating the Notification from MainActivity. |
| */ |
| private NotificationCompat.Builder recreateBuilderWithMessagingStyle() { |
| |
| // Main steps for building a MESSAGING_STYLE notification: |
| // 0. Get your data |
| // 1. Create/Retrieve Notification Channel for O and beyond devices (26+) |
| // 2. Build the MESSAGING_STYLE |
| // 3. Set up main Intent for notification |
| // 4. Set up RemoteInput (users can input directly from notification) |
| // 5. Build and issue the notification |
| |
| // 0. Get your data (everything unique per Notification) |
| MockDatabase.MessagingStyleCommsAppData messagingStyleCommsAppData = |
| MockDatabase.getMessagingStyleData(getApplicationContext()); |
| |
| // 1. Create/Retrieve Notification Channel for O and beyond devices (26+). |
| String notificationChannelId = |
| NotificationUtil.createNotificationChannel(this, messagingStyleCommsAppData); |
| |
| // 2. Build the NotificationCompat.Style (MESSAGING_STYLE). |
| String contentTitle = messagingStyleCommsAppData.getContentTitle(); |
| |
| MessagingStyle messagingStyle = |
| new MessagingStyle(messagingStyleCommsAppData.getMe()) |
| /* |
| * <p>This API's behavior was changed in SDK version |
| * {@link Build.VERSION_CODES#P}. If your application's target version is |
| * less than {@link Build.VERSION_CODES#P}, setting a conversation title to |
| * a non-null value will make {@link #isGroupConversation()} return |
| * {@code true} and passing {@code null} will make it return {@code false}. |
| * This behavior can be overridden by calling |
| * {@link #setGroupConversation(boolean)} regardless of SDK version. |
| * In {@code P} and above, this method does not affect group conversation |
| * settings. |
| * |
| * In our case, we use the same title. |
| */ |
| .setConversationTitle(contentTitle); |
| |
| // Adds all Messages. |
| // Note: Messages include the text, timestamp, and sender. |
| for (MessagingStyle.Message message : messagingStyleCommsAppData.getMessages()) { |
| messagingStyle.addMessage(message); |
| } |
| |
| messagingStyle.setGroupConversation(messagingStyleCommsAppData.isGroupConversation()); |
| |
| // 3. Set up main Intent for notification. |
| Intent notifyIntent = new Intent(this, MessagingMainActivity.class); |
| |
| // When creating your Intent, you need to take into account the back state, i.e., what |
| // happens after your Activity launches and the user presses the back button. |
| |
| // There are two options: |
| // 1. Regular activity - You're starting an Activity that's part of the application's |
| // normal workflow. |
| |
| // 2. Special activity - The user only sees this Activity if it's started from a |
| // notification. In a sense, the Activity extends the notification by providing |
| // information that would be hard to display in the notification itself. |
| |
| // Even though this sample's MainActivity doesn't link to the Activity this Notification |
| // launches directly, i.e., it isn't part of the normal workflow, a chat app generally |
| // always links to individual conversations as part of the app flow, so we will follow |
| // option 1. |
| |
| // For an example of option 2, check out the BIG_TEXT_STYLE example. |
| |
| // For more information, check out our dev article: |
| // https://developer.android.com/training/notify-user/navigation.html |
| |
| TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); |
| // Adds the back stack |
| stackBuilder.addParentStack(MessagingMainActivity.class); |
| // Adds the Intent to the top of the stack |
| stackBuilder.addNextIntent(notifyIntent); |
| // Gets a PendingIntent containing the entire back stack |
| PendingIntent mainPendingIntent = |
| PendingIntent.getActivity(this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); |
| |
| // 4. Set up RemoteInput, so users can input (keyboard and voice) from notification. |
| |
| // Note: For API <24 (M and below) we need to use an Activity, so the lock-screen present |
| // the auth challenge. For API 24+ (N and above), we use a Service (could be a |
| // BroadcastReceiver), so the user can input from Notification or lock-screen (they have |
| // choice to allow) without leaving the notification. |
| |
| // Create the RemoteInput specifying this key. |
| String replyLabel = getString(R.string.reply_label); |
| RemoteInput remoteInput = |
| new RemoteInput.Builder(MessagingIntentService.EXTRA_REPLY) |
| .setLabel(replyLabel) |
| // Use machine learning to create responses based on previous messages. |
| .setChoices(messagingStyleCommsAppData.getReplyChoicesBasedOnLastMessage()) |
| .build(); |
| |
| // Pending intent = |
| // API <24 (M and below): activity so the lock-screen presents the auth challenge. |
| // API 24+ (N and above): this should be a Service or BroadcastReceiver. |
| PendingIntent replyActionPendingIntent; |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| Intent intent = new Intent(this, MessagingIntentService.class); |
| intent.setAction(MessagingIntentService.ACTION_REPLY); |
| replyActionPendingIntent = PendingIntent.getService(this, 0, intent, 0); |
| |
| } else { |
| replyActionPendingIntent = mainPendingIntent; |
| } |
| |
| NotificationCompat.Action replyAction = |
| new NotificationCompat.Action.Builder( |
| R.drawable.ic_reply_white_18dp, |
| replyLabel, |
| replyActionPendingIntent) |
| .addRemoteInput(remoteInput) |
| // Informs system we aren't bringing up our own custom UI for a reply |
| // action. |
| .setShowsUserInterface(false) |
| // Allows system to generate replies by context of conversation. |
| .setAllowGeneratedReplies(true) |
| .setSemanticAction(Action.SEMANTIC_ACTION_REPLY) |
| .build(); |
| |
| // 5. Build and issue the notification. |
| |
| // Because we want this to be a new notification (not updating current notification), we |
| // create a new Builder. Later, we update this same notification, so we need to save this |
| // Builder globally (as outlined earlier). |
| |
| NotificationCompat.Builder notificationCompatBuilder = |
| new NotificationCompat.Builder(getApplicationContext(), notificationChannelId); |
| |
| GlobalNotificationBuilder.setNotificationCompatBuilderInstance(notificationCompatBuilder); |
| |
| notificationCompatBuilder |
| // MESSAGING_STYLE sets title and content for API 16 and above devices. |
| .setStyle(messagingStyle) |
| // Title for API < 16 devices. |
| .setContentTitle(contentTitle) |
| // Content for API < 16 devices. |
| .setContentText(messagingStyleCommsAppData.getContentText()) |
| .setSmallIcon(R.drawable.ic_launcher) |
| .setLargeIcon( |
| BitmapFactory.decodeResource( |
| getResources(), R.drawable.ic_person_black_48dp)) |
| .setContentIntent(mainPendingIntent) |
| .setDefaults(NotificationCompat.DEFAULT_ALL) |
| // Set primary color (important for Wear 2.0 Notifications). |
| .setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary)) |
| |
| // SIDE NOTE: Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) |
| // devices and all Wear devices. If you have more than one notification and |
| // you prefer a different summary notification, set a group key and create a |
| // summary notification via |
| // .setGroupSummary(true) |
| // .setGroup(GROUP_KEY_YOUR_NAME_HERE) |
| |
| // Number of new notifications for API <24 (M and below) devices. |
| .setSubText(Integer.toString(messagingStyleCommsAppData.getNumberOfNewMessages())) |
| .addAction(replyAction) |
| .setCategory(Notification.CATEGORY_MESSAGE) |
| |
| // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for |
| // 'importance' which is set in the NotificationChannel. The integers representing |
| // 'priority' are different from 'importance', so make sure you don't mix them. |
| .setPriority(messagingStyleCommsAppData.getPriority()) |
| |
| // Sets lock-screen visibility for 25 and below. For 26 and above, lock screen |
| // visibility is set in the NotificationChannel. |
| .setVisibility(messagingStyleCommsAppData.getChannelLockscreenVisibility()); |
| |
| // If the phone is in "Do not disturb" mode, the user may still be notified if the |
| // sender(s) are in a group allowed through "Do not disturb" by the user. |
| for (Person name : messagingStyleCommsAppData.getParticipants()) { |
| notificationCompatBuilder.addPerson(name.getUri()); |
| } |
| |
| return notificationCompatBuilder; |
| } |
| } |