How to stack Firebase Cloud Messaging notifications when the application is not running?
Solution 1:
To stack two or more notifications(specified in messages list) and to make them appear like GMail style notifications, you can add inbox style for your notification, like below:-
private void showNotification(Context mContext, String title, List messages, String timeStamp, PendingIntent resultPendingIntent, Uri alarmSound) {
final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
mContext);
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
for(int i=0;i<messages.size();i++)
inboxStyle.addLine(messages.get(i));
Notification notification;
notification = mBuilder.setTicker(title)
.setAutoCancel(true)
.setContentTitle(title)
.setContentIntent(resultPendingIntent)
.setSound(alarmSound)
.setStyle(inboxStyle)
.setWhen(getTimeMilliSec(timeStamp))
.setSmallIcon(R.drawable.notification_small_icon)
.setLargeIcon(R.drawable.notification_large_icon)
.setDeleteIntent(PendingIntent.getBroadcast(mContext,101,new Intent(mContext, NotificationDismissedReceiver.class),PendingIntent.FLAG_CANCEL_CURRENT))
.build();
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NConfig.NOTIFICATION_ID, notification);
}
If you notice, i have also added delete intent for my notification which triggers NotificationDismissedReceiver(BroadcastReceiver) whose main job is to clear notifications messages that have been dismissed by swipe gesture, so that next time only new notification messages get stack together.
public class NotificationDismissedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
messages.clear();
}
}
The main logic is to collect all unread/unswiped notifications inside a list i.e messages, below is onMessageReceive() of FirebaseMessagingService :-
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.e(TAG, "From: " + remoteMessage.getFrom());
if (remoteMessage == null)
return;
if (remoteMessage.getData()!=null && remoteMessage.getData().size() > 0)
{
try {
JSONObject json = new JSONObject(remoteMessage.getData().toString());
Log.e(TAG, "Notification Data: " + json);
Title = json.get("title").toString();
Message = json.get("body").toString();
messages.add(Message);
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
}
}
showNotification(...);
}
When the application is in foreground, the above onMessageReceive() of FirebaseMessagingService executes fine but when your application is in background or killed it doesn't execute. To make it execute you have to omit notification part from the JSON message sent from server side and only include the data part, as shown below:-
var data = new
{
to = token,
// notification = new
// {
// body = messageBody, //Omitting notification part of data
// title = messageTitle,
// icon = "myicon",
//},
data = new
{
body = messageBody, // adding all notification information inside data
title = messageTitle,
icon = "myicon",
}
};
By doing this your message now becomes data message only which means that it will always execute onMessageReceive() of FirebaseMessagingService irrespective of whether your app is in background or foreground.
Hope this much explanation helps.
Solution 2:
Firebase will not call your onMessageReceived
when your app is in the background or killed, and you can't customise your notification. System generated notification will show.
to make Firebase library to call your onMessageReceived
in every case
a) Foreground
b) Background
c) Killed
you must not put JSON key "notification" in your request to firebase API but instead use "data", see below.
For example, following message will not call onMessageReceived()
{
"to": "/topics/test",
"notification": {
"title" : "title",
"message": "data!"
}
}
but this will work
{
"to": "/topics/test",
"data": {
"title":"title",
"message":"data!"
}
}
see this it has a detailed description of firebase message type For example:
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message").toString(), remoteMessage.getData().get("title").toString());
}
}
private void sendNotification(String message, String title) {
int requestID = (int) System.currentTimeMillis();
Intent intent = new Intent(this, activityCompat);
PendingIntent pendingIntent = PendingIntent.getActivity(this, requestID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.small_logo)
.setContentTitle(title)
.setContentText(message).setContentIntent(pendingIntent)
.setAutoCancel(true)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(messageBody))
.setTicker(messageBody);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
Notification notification = notificationBuilder.build();
notificationManager.notify(0, notification);
}
Solution 3:
You can use the following code to group the notification. I'm using an Android Web view application so that I'm passing a URL as intent content.
I've used 2 types of notification grouping based on the Android versions because of android nougat and later versions automatically group the notification based on the group id but Marshmallow and earlier will not group the notifications. Remember one thing, you should send your notifications as data notifications. because you can handle the data notifications inside onMessageReceived
even app is in background or killed state.
My Firebase message handling service as follows:
import static com.packageName.config.AppConstant.MY_NOTIFICATION;
public class MyFireBaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFireBaseService";
private static final int SUMMARY_ID = 999;
@Override
public void onNewToken(String refreshedToken) {
super.onNewToken(refreshedToken);
//Store fcm token to shared preferences
SharedPrefManager.getInstance(getApplicationContext()).setFCMToken(refreshedToken);
}
@Override
public void onCreate() {
super.onCreate();
}
/* Data messages should be in the form of
* {
* type(Required) : "NotificationDTO type"
* title(Required) : "NotificationDTO title"
* message(Required) : "Message to be displayed in the notification panel"
* notificationURL(Required) : "Url to be loaded into the web view"
* groupId(Optional) : "Based on this group id, system will group the notification"
* channelId(optional) : "This channel id will be used to send notification"
* image(optional) : "This image will be displayed on notification panel"
* label(optional) : "NotificationDTO label"
* priority(optional) : "NotificationDTO priority. If notification priority not mentioned,
* Then default priority will be assigned to the notification"
* }
*/
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
boolean isForeGround = false;
super.onMessageReceived(remoteMessage);
// Fetching data part from the notification
Map<String, String> data = remoteMessage.getData();
String message = data.get("message");
String id = data.get("notificationId");
int notificationId;
// If notification id is empty then no need to show a notification
if (id == null || id.isEmpty()) {
return;
} else {
notificationId = Integer.parseInt(id);
}
if (message == null || message.equals("")) {
message = getString(R.string.default_notification_message);
}
String notificationURL = data.get("notificationURL");
String title = data.get("title");
// Group id should be a string
String groupKey = AppConstant.GROUP_KEY_NOTIFICATION;
if (data.get("groupKey") != null) {
groupKey = data.get("groupKey");
}
// Current we have only one channel with id `general_notification_id`
String channelId = data.get("channelId");
String label = data.get("label");
String image = data.get("image");
/*
* Notification priority(String Value) should be one of the following
* PRIORITY_HIGH/PRIORITY_LOW/PRIORITY_MAX/PRIORITY_MIN
* If no priority mentioned, system will automatically assign the default priority
*/
String priority = data.get("priority");
int notificationPriority = 0;
if (priority != null && !priority.isEmpty()) {
priority = priority.toUpperCase();
switch (priority) {
case "PRIORITY_HIGH":
notificationPriority = NotificationCompat.PRIORITY_HIGH;
break;
case "PRIORITY_LOW":
notificationPriority = NotificationCompat.PRIORITY_LOW;
break;
case "PRIORITY_MAX":
notificationPriority = NotificationCompat.PRIORITY_MAX;
break;
case "PRIORITY_MIN":
notificationPriority = NotificationCompat.PRIORITY_MIN;
break;
default:
notificationPriority = NotificationCompat.PRIORITY_DEFAULT;
break;
}
}
/*
* Category should be from the following list.
* Because system will sort the notification based on the category.
*
* CATEGORY_ALARM,CATEGORY_CALL,CATEGORY_MESSAGE,CATEGORY_EMAIL,CATEGORY_EVENT,
* CATEGORY_PROMO,CATEGORY_ALARM,CATEGORY_PROGRESS,CATEGORY_SOCIAL,CATEGORY_ERROR,
* CATEGORY_TRANSPORT,CATEGORY_SYSTEM,CATEGORY_SERVICE,CATEGORY_REMINDER,
* CATEGORY_RECOMMENDATION,CATEGORY_STATUS
*/
String category = data.get("category");
String notificationCategory = "";
if (category != null && !category.isEmpty()) {
category = category.toUpperCase();
switch (category) {
case "CATEGORY_ALARM":
notificationCategory = NotificationCompat.CATEGORY_ALARM;
break;
case "CATEGORY_CALL":
notificationCategory = NotificationCompat.CATEGORY_CALL;
break;
case "CATEGORY_MESSAGE":
notificationCategory = NotificationCompat.CATEGORY_MESSAGE;
break;
case "CATEGORY_EMAIL":
notificationCategory = NotificationCompat.CATEGORY_EMAIL;
break;
case "CATEGORY_EVENT":
notificationCategory = NotificationCompat.CATEGORY_EVENT;
break;
case "CATEGORY_PROMO":
notificationCategory = NotificationCompat.CATEGORY_PROMO;
break;
case "CATEGORY_PROGRESS":
notificationCategory = NotificationCompat.CATEGORY_PROGRESS;
break;
case "CATEGORY_SOCIAL":
notificationCategory = NotificationCompat.CATEGORY_SOCIAL;
break;
case "CATEGORY_ERROR":
notificationCategory = NotificationCompat.CATEGORY_ERROR;
break;
case "CATEGORY_TRANSPORT":
notificationCategory = NotificationCompat.CATEGORY_TRANSPORT;
break;
case "CATEGORY_SYSTEM":
notificationCategory = NotificationCompat.CATEGORY_SYSTEM;
break;
case "CATEGORY_SERVICE":
notificationCategory = NotificationCompat.CATEGORY_SERVICE;
break;
case "CATEGORY_RECOMMENDATION":
notificationCategory = NotificationCompat.CATEGORY_RECOMMENDATION;
break;
case "CATEGORY_REMINDER":
notificationCategory = NotificationCompat.CATEGORY_REMINDER;
break;
case "CATEGORY_STATUS":
notificationCategory = NotificationCompat.CATEGORY_STATUS;
break;
}
}
// Default notification visibility is private
String visibility = data.get("visibility");
int notificationVisibility = 0;
if (visibility != null && !visibility.isEmpty()) {
visibility = visibility.toUpperCase();
switch (visibility) {
case "VISIBILITY_PUBLIC":
notificationVisibility = NotificationCompat.VISIBILITY_PUBLIC;
break;
case "VISIBILITY_SECRET":
notificationVisibility = NotificationCompat.VISIBILITY_SECRET;
break;
default:
notificationVisibility = NotificationCompat.VISIBILITY_PRIVATE;
break;
}
}
//creating default notification url for grouped notifications
// if notification grouped, user cannot go the url corresponding to the each notification therefore assign a common url for the notification
String defaultNotificationURL = "https://something.com"
// Creating notification object
NotificationDTO notificationDTO = new NotificationDTO(
notificationId,
groupKey,
message,
notificationURL,
channelId,
image,
label,
notificationPriority,
title,
notificationCategory,
notificationVisibility,
defaultNotificationURL);
// Checking app is in foreground or background
// if the app in the foreground this message service send a broadcast message
// else app will create a notification in notification panel
try {
isForeGround = new ForegroundCheckTask().execute(this).get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//Android implement new grouping and channel mechanisms after android API version 24,
//So we need to implement different notification settings for both above 24 and below 24
if (android.os.Build.VERSION.SDK_INT >= 24) {
createNotificationForAPILevelAbove24(notificationDTO, isForeGround);
} else {
createNotificationForAPILevelBelow24(notificationDTO, isForeGround);
}
}
/**
* Creating notification for api level above 24
*
* @param notificationDTO NotificationDTO
* @param isForeGround Boolean
*/
private void createNotificationForAPILevelAbove24(NotificationDTO notificationDTO, Boolean isForeGround) {
Log.d(TAG, String.valueOf(isForeGround));
if (isForeGround) {
Intent intent = new Intent(MY_NOTIFICATION);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
} else {
int requestID = (int) System.currentTimeMillis();
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
PendingIntent resultIntent = PendingIntent.getActivity(this, requestID, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
String defaultChannel = getString(R.string.general_notification_id);
NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this, defaultChannel);
mNotificationBuilder.setSmallIcon(R.drawable.ic_stat_notification);
mNotificationBuilder.setColor(getResources().getColor(R.color.colorPrimary));
mNotificationBuilder.setContentTitle(notificationDTO.getTitle());
mNotificationBuilder.setContentText(notificationDTO.getMessage());
mNotificationBuilder.setGroup(notificationDTO.getGroupKey());
mNotificationBuilder.setAutoCancel(true);
mNotificationBuilder.setSound(notificationSoundURI);
mNotificationBuilder.setPriority(notificationDTO.getPriority());
if (notificationDTO.getImage() != null) {
Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
mNotificationBuilder.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap));
}
mNotificationBuilder.setContentIntent(resultIntent);
if (notificationDTO.getCategory() != null) {
mNotificationBuilder.setCategory(notificationDTO.getCategory());
}
mNotificationBuilder.setVisibility(notificationDTO.getVisibility());
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
boolean areNotificationsEnabled = notificationManager.areNotificationsEnabled();
String appPushEnabled = String.valueOf(areNotificationsEnabled);
notificationManager.notify(notificationDTO.getId(), mNotificationBuilder.build());
// Creating notification summary for grouping notifications
Notification summaryNotification =
new NotificationCompat.Builder(this, defaultChannel)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_stat_notification)
//specify which group this notification belongs to
.setGroup(notificationDTO.getGroupKey())
//set this notification as the summary for the group
.setGroupSummary(true)
//automatically remove the notifications from the notification tray
.setAutoCancel(true)
.build();
notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summaryNotification);
}
}
/**
* Handling notification for api level below 24
*
* @param notificationDTO NotificationDTO
* @param isForeGround Boolean
*/
private void createNotificationForAPILevelBelow24(NotificationDTO notificationDTO, Boolean isForeGround) {
if (isForeGround) {
Intent intent = new Intent(MY_NOTIFICATION);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
} else {
//Grouping notifications
String storedNotifications = SharedPrefManager.getInstance(this).getNotifications();
JSONArray notificationArray;
try {
boolean isDuplicateNotification = false;
JSONObject notificationObject = new JSONObject();
notificationObject.put("notificationId", notificationDTO.getId());
notificationObject.put("description", notificationDTO.getMessage());
notificationObject.put("title", notificationDTO.getTitle());
if (storedNotifications != null && !storedNotifications.equals("")) {
Log.d(TAG, storedNotifications);
notificationArray = new JSONArray(storedNotifications);
for (int i = 0; i < notificationArray.length(); i++) {
JSONObject json = notificationArray.getJSONObject(i);
if (json.getInt("notificationId") == notificationDTO.getId()) {
isDuplicateNotification = true;
break;
}
}
} else {
notificationArray = new JSONArray();
}
if (isDuplicateNotification) {
//Notification already added to the tray
return;
}
notificationArray.put(notificationObject);
SharedPrefManager.getInstance(this).setNotificationDetails(notificationArray.toString());
Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder summary = new NotificationCompat.Builder(this);
summary.setSmallIcon(R.drawable.ic_stat_notification);
summary.setGroup(notificationDTO.getGroupKey());
summary.setAutoCancel(true);
summary.setPriority(notificationDTO.getPriority());
summary.setColor(ContextCompat.getColor(this, R.color.colorPrimary));
summary.setSound(notificationSoundURI);
summary.setContentTitle(notificationDTO.getTitle());
summary.setContentText(notificationDTO.getMessage());
summary.setPriority(notificationDTO.getPriority());
if (notificationDTO.getCategory() != null) {
summary.setCategory(notificationDTO.getCategory());
}
summary.setVisibility(notificationDTO.getVisibility());
if (notificationDTO.getImage() != null) {
Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
summary.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap));
}
/*
* This is used to pass notification url to the main class of the application.
* Based on this url MainActivity load the corresponding url into the web view
*/
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
/*
* checking more than 2 notifications received by the system,
* then this will create a summary of that notifications.
* else create a single notification
*/
if (notificationArray.length() > 1) {
summary.setGroupSummary(true);
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.setBigContentTitle(getString(R.string.app_name));
summary.setStyle(inboxStyle);
int messageCount;
for (messageCount = 0; messageCount < notificationArray.length(); messageCount++) {
JSONObject json = notificationArray.getJSONObject(messageCount);
inboxStyle.addLine(json.getString("title") + " " + json.getString("description"));
}
inboxStyle.setSummaryText(String.valueOf
(messageCount) + " notifications");
summary.setNumber(messageCount);
summary.setContentText(String.valueOf(messageCount + " notifications"));
intent.putExtra("notificationURL", notificationDTO.getDefaultNotificationUrl());
} else {
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
}
PendingIntent resultIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
summary.setContentIntent(resultIntent);
/*
* One cancel intent is used to clear the notifications stored in
* the shared preferences when user delete the notifications.
*/
Intent onCancelIntent = new Intent(this, OnCancelBroadcastReceiver.class);
PendingIntent onDismissPendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, onCancelIntent, 0);
summary.setDeleteIntent(onDismissPendingIntent);
notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summary.build());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Used to load image from notification
*
* @param imageUrl String
* @return Bitmap
*/
public Bitmap getBitmapFromUrl(String imageUrl) {
try {
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (Exception e) {
return null;
}
}
}
Notification object class like follows:
public class NotificationDTO {
private String groupKey, message, notificationURL, channelId;
private String image, label, title, category,defaultNotificationUrl;
private int priority, id, visibility;
public NotificationDTO(
int id,
String groupKey,
String message,
String notificationURL,
String channelId,
String image,
String label,
int priority,
String title,
String category,
int visibility,
String defaultNotificationUrl) {
this.groupKey = groupKey;
this.message = message;
this.id = id;
this.notificationURL = notificationURL;
this.channelId = channelId;
this.image = image;
this.label = label;
this.priority = priority;
this.title = title;
this.category = category;
this.visibility = visibility;
this.defaultNotificationUrl = defaultNotificationUrl;
}
public String getGroupKey() {
return groupKey;
}
public String getMessage() {
return message;
}
public String getNotificationURL() {
return notificationURL;
}
public String getChannelId() {
return channelId;
}
public String getLabel() {
return label;
}
public String getImage() {
return image;
}
public int getPriority() {
return priority;
}
public String getTitle() {
return title;
}
public String getCategory() {
return category;
}
public int getId() {
return id;
}
public int getVisibility() {
return visibility;
}
public String getDefaultNotificationUrl() {
return defaultNotificationUrl;
}
}
SharedPreference manager like follows:
public class SharedPrefManager {
private static final String KEY_FCM_TOKEN = "keyFCMToken";
private static final String KEY_NOTIFICATIONS = "keyNotifications";
private static SharedPrefManager mInstance;
private static Context mContext;
private SharedPrefManager(Context context) {
mContext = context;
}
public static synchronized SharedPrefManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new SharedPrefManager(context);
}
return mInstance;
}
public void setNotificationDetails(String descriptions) {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_NOTIFICATIONS, descriptions);
editor.apply();
}
public void setFCMToken(String fcmToken) {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_FCM_TOKEN, fcmToken);
editor.apply();
}
public String getNotifications() {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(KEY_NOTIFICATIONS, null);
}
public String getFCMToken() {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(KEY_FCM_TOKEN, null);
}
}
and for creating channels for notification I'm using an app controller class that extends application class.
public class AppController extends Application {
public static final String TAG = AppController.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
/*
Define notification channels here.
*/
//NotificationDTO channel is necessary
//create a notification channel id in res/values/strings.xml
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String defaultChannel = getString(R.string.general_notification_id);
String channelName = getString(R.string.general_notification_name);
// String miscellaneousChannel = getString(R.string.miscellaneous_notification_id);
// String miscellaneousChannelName = getString(R.string.miscellaneous_notification_name);
NotificationManager notificationManager =
getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(new NotificationChannel(defaultChannel,
channelName, NotificationManager.IMPORTANCE_DEFAULT));
}
}
}
A broadcast receiver implemented to clear the shared preferences like follows
public class OnCancelBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("ON_CANCEL","Cancelled");
SharedPrefManager.getInstance(context).setNotificationDetails("");
}
}