notifications: improve behavior

* don't mark messages as read on notification dismiss
* fix conversation visibility flag update
* fix display of inbox-style messages

Change-Id: I983484a9fa6132ffe30aa7d7ba85be5c1b3d9e90
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
index 7570fb2..a6df8ca 100644
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
@@ -92,6 +92,7 @@
     static public final String ACTION_CALL_VIEW = BuildConfig.APPLICATION_ID + ".action.CALL_VIEW";
 
     static public final String ACTION_CONV_READ = BuildConfig.APPLICATION_ID + ".action.CONV_READ";
+    static public final String ACTION_CONV_DISMISS = BuildConfig.APPLICATION_ID + ".action.CONV_DISMISS";
     static public final String ACTION_CONV_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CONV_ACCEPT";
 
     private static final String TAG = DRingService.class.getName();
@@ -617,6 +618,7 @@
                 break;
             case ACTION_CONV_READ:
             case ACTION_CONV_ACCEPT:
+            case ACTION_CONV_DISMISS:
                 extras = intent.getExtras();
                 if (extras != null) {
                     handleConvAction(intent.getAction(), intent.getExtras());
@@ -706,6 +708,8 @@
                     mNotificationService.cancelTextNotification(ringId);
                 }
                 break;
+            case ACTION_CONV_DISMISS:
+                break;
             case ACTION_CONV_ACCEPT:
                 startActivity(new Intent(Intent.ACTION_VIEW)
                         .putExtras(extras)
diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
index f18bb38..3629706 100644
--- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
@@ -38,9 +38,11 @@
 import android.support.v4.app.NotificationManagerCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.text.Html;
+import android.text.Spanned;
 import android.text.format.DateUtils;
 import android.util.SparseArray;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Random;
@@ -89,6 +91,7 @@
     @Inject
     DeviceRuntimeService mDeviceRuntimeService;
     private NotificationManagerCompat notificationManager;
+    private Random random;
 
     public void initHelper() {
         if (notificationManager == null) {
@@ -98,6 +101,7 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             registerNotificationChannels();
         }
+        random = new Random();
     }
 
     @RequiresApi(api = Build.VERSION_CODES.O)
@@ -144,7 +148,7 @@
         notificationManager.cancel(notificationId);
 
         PendingIntent gotoIntent = PendingIntent.getService(mContext,
-                new Random().nextInt(),
+                random.nextInt(),
                 new Intent(DRingService.ACTION_CALL_VIEW)
                         .setClass(mContext, DRingService.class)
                         .putExtra(KEY_CALL_ID, call.getCallId()), 0);
@@ -155,7 +159,7 @@
                     .setContentText(mContext.getText(R.string.notif_current_call))
                     .setContentIntent(gotoIntent)
                     .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_hangup),
-                            PendingIntent.getService(mContext, new Random().nextInt(),
+                            PendingIntent.getService(mContext, random.nextInt(),
                                     new Intent(DRingService.ACTION_CALL_END)
                                             .setClass(mContext, DRingService.class)
                                             .putExtra(KEY_CALL_ID, call.getCallId()),
@@ -169,13 +173,13 @@
                         .setContentIntent(gotoIntent)
                         .setFullScreenIntent(gotoIntent, true)
                         .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_decline),
-                                PendingIntent.getService(mContext, new Random().nextInt(),
+                                PendingIntent.getService(mContext, random.nextInt(),
                                         new Intent(DRingService.ACTION_CALL_REFUSE)
                                                 .setClass(mContext, DRingService.class)
                                                 .putExtra(KEY_CALL_ID, call.getCallId()),
                                         PendingIntent.FLAG_ONE_SHOT))
                         .addAction(R.drawable.ic_action_accept, mContext.getText(R.string.action_call_accept),
-                                PendingIntent.getService(mContext, new Random().nextInt(),
+                                PendingIntent.getService(mContext, random.nextInt(),
                                         new Intent(DRingService.ACTION_CALL_ACCEPT)
                                                 .setClass(mContext, DRingService.class)
                                                 .putExtra(KEY_CALL_ID, call.getCallId()),
@@ -186,7 +190,7 @@
                         .setContentText(mContext.getText(R.string.notif_outgoing_call))
                         .setContentIntent(gotoIntent)
                         .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_hangup),
-                                PendingIntent.getService(mContext, new Random().nextInt(),
+                                PendingIntent.getService(mContext, random.nextInt(),
                                         new Intent(DRingService.ACTION_CALL_END)
                                                 .setClass(mContext, DRingService.class)
                                                 .putExtra(KEY_CALL_ID, call.getCallId()),
@@ -219,13 +223,18 @@
 
     @Override
     public void showTextNotification(CallContact contact, Conversation conversation, TreeMap<Long, TextMessage> texts) {
+        if (texts.isEmpty()) {
+            cancelTextNotification(contact);
+            return;
+        }
+        TextMessage last = texts.lastEntry().getValue();
 
         Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT)
                 .setClass(mContext, DRingService.class)
                 .putExtra(ConversationFragment.KEY_ACCOUNT_ID, conversation.getLastAccountUsed())
                 .putExtra(ConversationFragment.KEY_CONTACT_RING_ID, contact.getPhones().get(0).getNumber().toString());
 
-        Intent intentDelete = new Intent(DRingService.ACTION_CONV_READ)
+        Intent intentDelete = new Intent(DRingService.ACTION_CONV_DISMISS)
                 .setClass(mContext, DRingService.class)
                 .putExtra(ConversationFragment.KEY_ACCOUNT_ID, conversation.getLastAccountUsed())
                 .putExtra(ConversationFragment.KEY_CONTACT_RING_ID, contact.getPhones().get(0).getNumber().toString());
@@ -236,9 +245,12 @@
                 .setDefaults(NotificationCompat.DEFAULT_ALL)
                 .setSmallIcon(R.drawable.ic_ring_logo_white)
                 .setContentTitle(contact.getDisplayName())
-                .setContentIntent(PendingIntent.getService(mContext, new Random().nextInt(), intentConversation, 0))
-                .setDeleteIntent(PendingIntent.getService(mContext, new Random().nextInt(), intentDelete, 0))
-                .setAutoCancel(true);
+                .setContentText(last.getMessage())
+                .setWhen(last.getTimestamp())
+                .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
+                .setDeleteIntent(PendingIntent.getService(mContext, random.nextInt(), intentDelete, 0))
+                .setAutoCancel(true)
+                .setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
 
         if (contact.getPhoto() != null) {
             Resources res = mContext.getResources();
@@ -251,29 +263,31 @@
             }
         }
         if (texts.size() == 1) {
-            TextMessage txt = texts.firstEntry().getValue();
-            txt.setNotified(true);
-            messageNotificationBuilder.setContentText(txt.getMessage());
+            last.setNotified(true);
             messageNotificationBuilder.setStyle(null);
-            messageNotificationBuilder.setWhen(txt.getTimestamp());
         } else {
-            NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
-            for (TextMessage s : texts.values()) {
-                inboxStyle.addLine(Html.fromHtml("<b>" + DateUtils.formatDateTime(mContext, s.getTimestamp(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL) + "</b> " + s.getMessage()));
-                s.setNotified(true);
+            ArrayList<Spanned> txts = new ArrayList<>(3);
+            int i = 0;
+            for (TextMessage textMessage : texts.descendingMap().values()) {
+                if (i == 5)
+                    break;
+                txts.add(0, Html.fromHtml("<b>" + DateUtils.formatDateTime(mContext, textMessage.getTimestamp(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL) + "</b> " + textMessage.getMessage()));
+                textMessage.setNotified(true);
+                i++;
             }
-            messageNotificationBuilder.setContentText(texts.lastEntry().getValue().getMessage());
+            NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
+            for (Spanned spanned : txts) {
+                inboxStyle.addLine(spanned);
+            }
             messageNotificationBuilder.setStyle(inboxStyle);
-            messageNotificationBuilder.setWhen(texts.lastEntry().getValue().getTimestamp());
         }
 
-        messageNotificationBuilder.setColor(ResourcesCompat.getColor(mContext.getResources(),
-                R.color.color_primary_dark, null));
+        Intent intentRead = new Intent(DRingService.ACTION_CONV_READ)
+                .setClass(mContext, DRingService.class)
+                .putExtra(ConversationFragment.KEY_ACCOUNT_ID, conversation.getLastAccountUsed())
+                .putExtra(ConversationFragment.KEY_CONTACT_RING_ID, contact.getPhones().get(0).getNumber().toString());
 
-        // Reuse intentDelete for the 'Mark as read' action
-        PendingIntent pIntent = PendingIntent.getService(mContext, (int) System.currentTimeMillis(), intentDelete, 0);
-        messageNotificationBuilder.addAction(0, mContext.getString(R.string.notif_mark_as_read), pIntent);
-
+        messageNotificationBuilder.addAction(0, mContext.getString(R.string.notif_mark_as_read), PendingIntent.getService(mContext, Long.valueOf(System.currentTimeMillis()).intValue(), intentRead, 0));
         int notificationId = getTextNotificationId(contact);
         notificationManager.notify(notificationId, messageNotificationBuilder.build());
         mNotificationBuilders.put(notificationId, messageNotificationBuilder);
@@ -301,19 +315,19 @@
             info.putString(TRUST_REQUEST_NOTIFICATION_FROM, request.getContactId());
             messageNotificationBuilder.setContentText(request.getDisplayname())
                     .addAction(R.drawable.ic_action_accept, mContext.getText(R.string.accept),
-                            PendingIntent.getService(mContext, new Random().nextInt(),
+                            PendingIntent.getService(mContext, random.nextInt(),
                                     new Intent(DRingService.ACTION_TRUST_REQUEST_ACCEPT)
                                             .setClass(mContext, DRingService.class)
                                             .putExtras(info),
                                     PendingIntent.FLAG_ONE_SHOT))
                     .addAction(R.drawable.ic_delete_white, mContext.getText(R.string.refuse),
-                            PendingIntent.getService(mContext, new Random().nextInt(),
+                            PendingIntent.getService(mContext, random.nextInt(),
                                     new Intent(DRingService.ACTION_TRUST_REQUEST_REFUSE)
                                             .setClass(mContext, DRingService.class)
                                             .putExtras(info),
                                     PendingIntent.FLAG_ONE_SHOT))
                     .addAction(R.drawable.ic_close_white, mContext.getText(R.string.block),
-                            PendingIntent.getService(mContext, new Random().nextInt(),
+                            PendingIntent.getService(mContext, random.nextInt(),
                                     new Intent(DRingService.ACTION_TRUST_REQUEST_BLOCK)
                                             .setClass(mContext, DRingService.class)
                                             .putExtras(info),
@@ -349,7 +363,7 @@
                 .setClass(mContext, HomeActivity.class)
                 .putExtra(ContactRequestsFragment.ACCOUNT_ID, account.getAccountID());
         messageNotificationBuilder.setContentIntent(PendingIntent.getActivity(mContext,
-                new Random().nextInt(), intentOpenTrustRequestFragment, PendingIntent.FLAG_ONE_SHOT));
+                random.nextInt(), intentOpenTrustRequestFragment, PendingIntent.FLAG_ONE_SHOT));
 
         messageNotificationBuilder.setColor(ResourcesCompat.getColor(mContext.getResources(),
                 R.color.color_primary_dark, null));
diff --git a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
index 188203b..97e0b95 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
@@ -114,10 +114,9 @@
 
     public void pause() {
         if (mConversation != null) {
-            mHistoryService.readMessages(mConversation);
             Conversation localConversation = mConversationFacade.getConversationByContact(mContactService.getContact(new Uri(mContactRingId)));
             if (localConversation != null) {
-                localConversation.setVisible(true);
+                localConversation.setVisible(false);
             }
         }
     }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
index 713cb9e..4d705e4 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
@@ -25,7 +25,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.NavigableMap;
 import java.util.TreeMap;
 
 import javax.inject.Inject;
@@ -36,7 +35,6 @@
 import cx.ring.model.ConfigKey;
 import cx.ring.model.Conversation;
 import cx.ring.model.HistoryCall;
-import cx.ring.model.HistoryEntry;
 import cx.ring.model.HistoryText;
 import cx.ring.model.SecureSipCall;
 import cx.ring.model.ServiceEvent;
@@ -228,18 +226,15 @@
         Log.d(TAG, "updateTextNotifications()");
 
         for (Conversation conversation : mConversationMap.values()) {
-
-            if (conversation.isVisible()) {
-                mNotificationService.cancelTextNotification(conversation.getContact());
-                continue;
-            }
             TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages();
-            if (texts.isEmpty() || texts.lastEntry().getValue().isNotified()) {
-                continue;
-            } else {
-                mNotificationService.cancelTextNotification(conversation.getContact());
-            }
 
+            if (texts.isEmpty() || conversation.isVisible()) {
+                mNotificationService.cancelTextNotification(conversation.getContact());
+                continue;
+            }
+            if (texts.lastEntry().getValue().isNotified()) {
+                continue;
+            }
             CallContact contact = conversation.getContact();
             mNotificationService.showTextNotification(contact, conversation, texts);
         }
@@ -265,6 +260,9 @@
         }
 
         conversation.addTextMessage(txt);
+        if (txt.isRead()) {
+            mHistoryService.updateTextMessage(new HistoryText(txt));
+        }
     }
 
     private void parseHistoryCalls(List<HistoryCall> historyCalls, boolean acceptAllMessages) {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
index d901dd4..0217936 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
@@ -187,6 +187,9 @@
     }
 
     public void addTextMessage(TextMessage txt) {
+        if (mVisible) {
+            txt.read();
+        }
         if (txt.getContact() == null) {
             txt.setContact(getContact());
         }