blob: 2f57fff79dc11305a4d5e30666c142c47db1ae1b [file] [log] [blame]
/*
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;
}
}