show notification during ongoing call

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/NotificationService.java        | 39 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java      | 14 
src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java               |  3 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java | 22 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java     | 20 
src/main/res/values/strings.xml                                               |  3 
6 files changed, 88 insertions(+), 13 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -76,6 +76,7 @@ public class NotificationService {
     private static final int NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 2;
     private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6;
     private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
+    private static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
     private final XmppConnectionService mXmppConnectionService;
     private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
     private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
@@ -166,6 +167,13 @@ public class NotificationService {
         incomingCallsChannel.setGroup("calls");
         notificationManager.createNotificationChannel(incomingCallsChannel);
 
+        final NotificationChannel ongoingCallsChannel = new NotificationChannel("ongoing_calls",
+                c.getString(R.string.ongoing_calls_channel_name),
+                NotificationManager.IMPORTANCE_LOW);
+        ongoingCallsChannel.setShowBadge(false);
+        ongoingCallsChannel.setGroup("calls");
+        notificationManager.createNotificationChannel(ongoingCallsChannel);
+
 
         final NotificationChannel messagesChannel = new NotificationChannel("messages",
                 c.getString(R.string.messages_channel_name),
@@ -333,7 +341,6 @@ public class NotificationService {
         fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
         fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mXmppConnectionService, 101, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
         final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "incoming_calls");
         builder.setSmallIcon(R.drawable.ic_call_white_24dp);
         builder.setContentTitle(mXmppConnectionService.getString(R.string.rtp_state_incoming_call));
@@ -346,7 +353,7 @@ public class NotificationService {
         builder.addAction(new NotificationCompat.Action.Builder(
                 R.drawable.ic_call_end_white_48dp,
                 mXmppConnectionService.getString(R.string.dismiss_call),
-                createDismissCall(id.sessionId, 102))
+                createCallAction(id.sessionId, XmppConnectionService.ACTION_DISMISS_CALL, 102))
                 .build());
         builder.addAction(new NotificationCompat.Action.Builder(
                 R.drawable.ic_call_white_24dp,
@@ -358,6 +365,26 @@ public class NotificationService {
         notify(INCOMING_CALL_NOTIFICATION_ID, builder.build());
     }
 
+    public void showOngoingCallNotification(final AbstractJingleConnection.Id id) {
+        final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls");
+        builder.setSmallIcon(R.drawable.ic_call_white_24dp);
+        builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
+        builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
+        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+        builder.setPriority(NotificationCompat.PRIORITY_HIGH);
+        builder.setCategory(NotificationCompat.CATEGORY_CALL);
+        builder.setContentIntent(createPendingRtpSession(id, Intent.ACTION_VIEW, 101));
+        builder.setOngoing(true);
+        builder.addAction(new NotificationCompat.Action.Builder(
+                R.drawable.ic_call_end_white_48dp,
+                mXmppConnectionService.getString(R.string.hang_up),
+                createCallAction(id.sessionId, XmppConnectionService.ACTION_END_CALL, 104))
+                .build());
+        final Notification notification = builder.build();
+        notification.flags = notification.flags | Notification.FLAG_INSISTENT;
+        notify(ONGOING_CALL_NOTIFICATION_ID, builder.build());
+    }
+
     private PendingIntent createPendingRtpSession(final AbstractJingleConnection.Id id, final String action, final int requestCode) {
         final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
         fullScreenIntent.setAction(action);
@@ -373,6 +400,10 @@ public class NotificationService {
         cancel(INCOMING_CALL_NOTIFICATION_ID);
     }
 
+    public void cancelOngoingCallNotification() {
+        cancel(ONGOING_CALL_NOTIFICATION_ID);
+    }
+
     private void pushNow(final Message message) {
         mXmppConnectionService.updateUnreadCountBadge();
         if (!notify(message)) {
@@ -899,9 +930,9 @@ public class NotificationService {
         return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 16), intent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
-    private PendingIntent createDismissCall(String sessionId, int requestCode) {
+    private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
         final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
-        intent.setAction(XmppConnectionService.ACTION_DISMISS_CALL);
+        intent.setAction(action);
         intent.setPackage(mXmppConnectionService.getPackageName());
         intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId);
         return PendingIntent.getService(mXmppConnectionService, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -167,6 +167,7 @@ public class XmppConnectionService extends Service {
     public static final String ACTION_FCM_TOKEN_REFRESH = "fcm_token_refresh";
     public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received";
     public static final String ACTION_DISMISS_CALL = "dismiss_call";
+    public static final String ACTION_END_CALL = "end_call";
     private static final String ACTION_POST_CONNECTIVITY_CHANGE = "eu.siacs.conversations.POST_CONNECTIVITY_CHANGE";
 
     private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp";
@@ -640,10 +641,17 @@ public class XmppConnectionService extends Service {
                         }
                     });
                     break;
-                case ACTION_DISMISS_CALL:
+                case ACTION_DISMISS_CALL: {
                     final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
-                    Log.d(Config.LOGTAG,"received intent to dismiss call with session id "+sessionId);
+                    Log.d(Config.LOGTAG, "received intent to dismiss call with session id " + sessionId);
                     mJingleConnectionManager.rejectRtpSession(sessionId);
+                }
+                break;
+                case ACTION_END_CALL: {
+                    final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
+                    Log.d(Config.LOGTAG, "received intent to end call with session id " + sessionId);
+                    mJingleConnectionManager.endRtpSession(sessionId);
+                }
                     break;
                 case ACTION_DISMISS_ERROR_NOTIFICATIONS:
                     dismissErrorNotifications();
@@ -3978,7 +3986,7 @@ public class XmppConnectionService extends Service {
     }
 
     public void notifyJingleRtpConnectionUpdate(final Account account, final Jid with, final String sessionId, final RtpEndUserState state) {
-        for(OnJingleRtpConnectionUpdate listener : threadSafeList(this.onJingleRtpConnectionUpdate)) {
+        for (OnJingleRtpConnectionUpdate listener : threadSafeList(this.onJingleRtpConnectionUpdate)) {
             listener.onJingleRtpConnectionUpdate(account, with, sessionId, state);
         }
     }

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java 🔗

@@ -237,6 +237,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
             finish();
             return;
         }
+        if (JingleRtpConnection.STATES_SHOWING_ONGOING_CALL.contains(requireRtpConnection().getState())) {
+            putScreenInCallMode();
+        }
         binding.with.setText(getWith().getDisplayName());
         updateStateDisplay(currentState);
         updateButtonConfiguration(currentState);

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -1,21 +1,19 @@
 package eu.siacs.conversations.xmpp.jingle;
 
+import android.util.Base64;
 import android.util.Log;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 
 import java.lang.ref.WeakReference;
+import java.security.SecureRandom;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.services.AbstractConnectionManager;
@@ -271,7 +269,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     }
 
     static String nextRandomId() {
-        return UUID.randomUUID().toString();
+        final byte[] id = new byte[16];
+        new SecureRandom().nextBytes(id);
+        return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING);
     }
 
     public void deliverIbbPacket(Account account, IqPacket packet) {
@@ -354,6 +354,16 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
     }
 
+    public void endRtpSession(final String sessionId) {
+        for (final AbstractJingleConnection connection : this.connections.values()) {
+            if (connection.getId().sessionId.equals(sessionId)) {
+                if (connection instanceof JingleRtpConnection) {
+                    ((JingleRtpConnection) connection).endCall();
+                }
+            }
+        }
+    }
+
     public void failProceed(Account account, final Jid with, String sessionId) {
         final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
         final AbstractJingleConnection existingJingleConnection = connections.get(id);
@@ -374,7 +384,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
 
         public static RtpSessionProposal of(Account account, Jid with) {
-            return new RtpSessionProposal(account, with, UUID.randomUUID().toString());
+            return new RtpSessionProposal(account, with, nextRandomId());
         }
 
         @Override

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -74,6 +74,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         VALID_TRANSITIONS = transitionBuilder.build();
     }
 
+    public static final List<State> STATES_SHOWING_ONGOING_CALL = Arrays.asList(
+            State.PROCEED,
+            State.SESSION_INITIALIZED,
+            State.SESSION_INITIALIZED_PRE_APPROVED,
+            State.SESSION_ACCEPTED
+    );
+
     private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this);
     private final ArrayDeque<IceCandidate> pendingIceCandidates = new ArrayDeque<>();
     private State state = State.NULL;
@@ -727,6 +734,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             this.state = target;
             Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into " + target);
             updateEndUserState();
+            updateOngoingCallNotification();
             return true;
         } else {
             return false;
@@ -759,6 +767,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState());
     }
 
+    private void updateOngoingCallNotification() {
+        if (STATES_SHOWING_ONGOING_CALL.contains(this.state)) {
+            xmppConnectionService.getNotificationService().showOngoingCallNotification(id);
+        } else {
+            xmppConnectionService.getNotificationService().cancelOngoingCallNotification();
+        }
+    }
+
     private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
         if (id.account.getXmppConnection().getFeatures().extendedServiceDiscovery()) {
             final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
@@ -815,6 +831,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         }
     }
 
+    public State getState() {
+        return this.state;
+    }
+
     private interface OnIceServersDiscovered {
         void onIceServersDiscovered(List<PeerConnection.IceServer> iceServers);
     }

src/main/res/values/strings.xml 🔗

@@ -751,6 +751,7 @@
     <string name="notification_group_calls">Calls</string>
     <string name="messages_channel_name">Messages</string>
     <string name="incoming_calls_channel_name">Incoming calls</string>
+    <string name="ongoing_calls_channel_name">Ongoing calls</string>
     <string name="silent_messages_channel_name">Silent messages</string>
     <string name="silent_messages_channel_description">This notification group is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period).</string>
     <string name="pref_more_notification_settings">Notification Settings</string>
@@ -899,6 +900,8 @@
     <string name="rtp_state_declined_or_busy">Busy</string>
     <string name="rtp_state_connectivity_error">Unable to connect call</string>
     <string name="rtp_state_application_failure">Application failure</string>
+    <string name="hang_up">Hang up</string>
+    <string name="ongoing_call">Ongoing call</string>
     <plurals name="view_users">
         <item quantity="one">View %1$d Participant</item>
         <item quantity="other">View %1$d Participants</item>