twilio / voice-quickstart-android

Quickstart app for the Voice Android SDK
https://www.twilio.com/docs/api/voice-sdk/android/getting-started
MIT License
184 stars 140 forks source link

Incoming call notification gets lost when there are too many notifications #365

Closed jc-devs closed 4 years ago

jc-devs commented 4 years ago

Description

When there are a lot of notifications from the app that uses Twilio SDK or the notifications generated from other apps. The incoming call notifications gets lost. The phone does ring, but there is no way to find that notification and eventually accept the call. This is happening on Samsung devices.

Steps to Reproduce

On a Samsung device, add a number of notifications from the Twilio app and other apps so that notification tray has atleast 20 notifications. Now place an incoming call, the notification to answer incoming call will not appear.

Code

Not sure what part of code we can share here

Expected Behavior

We expect that the notification should appear with some sort of priority so that one can answer incoming calls.

Actual Behavior

The phone rings with no notification to answer incoming calls.

Reproduces How Often

When the notification tray is full or has more than 20 notifications

Twilio Call SID(s)

Happens with all calls in a certain condition and hence call SIDs can't help

Logs

Not at this time

Versions

Samsung Android 10 (any device)

Voice Android SDK

5.4

OS Version

Android 10

Device Model

Samsung s20

kbagchiGWC commented 4 years ago

Hi @jc-devs,

Thanks for reaching out.

jc-devs commented 4 years ago

Hi @kbagchiGWC,

This is Prateek from JustCall team. We have talked before on emails and Github comments where you helped us solved an issue where incoming call was not presenting over lock screen. Since you have assigned this, I am sure I will get the resolution here 👍

  1. The end user is able to answer calls when there are no notifications or typically less than 20 notifications since the notification does appear in the tray and as well as a heads up notification.

  2. We are exactly using the same service as implemented under VoiceFirebaseMessagingService

  3. Similar to as mentioned above

Please find our VoiceFirebaseMessagingService code below

public class VoiceFirebaseMessagingService extends FirebaseMessagingService {

private static final String TAG = "VoiceFCMService";

@Override
public void onCreate() {
    super.onCreate();
    FirebaseCrashlytics.getInstance().setUserId(SaveSharedPreference.getPrefUserId(VoiceFirebaseMessagingService.this));
    FirebaseCrashlytics.getInstance().log(SaveSharedPreference.getPrefUserHash(VoiceFirebaseMessagingService.this));
}

/**
 * Called when message is received.
 *
 * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
 */
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    Log.d(TAG, "Received onMessageReceived()");
    Log.d(TAG, "Bundle data: " + remoteMessage.getData());
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        boolean valid = Voice.handleMessage(this, remoteMessage.getData(), new MessageListener() {
            @Override
            public void onCallInvite(@NonNull CallInvite callInvite) {
                final int notificationId = (int) System.currentTimeMillis();
                handleInvite(callInvite, notificationId);
            }

            @Override
            public void onCancelledCallInvite(@NonNull CancelledCallInvite cancelledCallInvite, @Nullable CallException callException) {
                final Intent intentring = new Intent(VoiceFirebaseMessagingService.this, RingtoneManagerService.class);
                intentring.putExtra("key", "stop");
                stopService(intentring);
                handleCanceledCallInvite(cancelledCallInvite);
            }
        });

        if (!valid) {
            Log.e(TAG, "The message was not a valid Twilio Voice SDK payload: " +
                    remoteMessage.getData());
        }
    }
}

@Override
public void onNewToken(String token) {
    super.onNewToken(token);
    Intent intent = new Intent(Constants.ACTION_FCM_TOKEN);
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

private void handleInvite(CallInvite callInvite, int notificationId) {
    Intent intent = new Intent(this, IncomingCallNotificationService.class);
    intent.setAction(Constants.ACTION_INCOMING_CALL);
    intent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId);
    intent.putExtra(Constants.INCOMING_CALL_INVITE, callInvite);
    startService(intent);
}

private void handleCanceledCallInvite(CancelledCallInvite cancelledCallInvite) {
    final Intent intentring = new Intent(VoiceFirebaseMessagingService.this, RingtoneManagerService.class);
    intentring.putExtra("key", "stop");
    stopService(intentring);
    Intent intent = new Intent(this, IncomingCallNotificationService.class);
    intent.setAction(Constants.ACTION_CANCEL_CALL);
    intent.putExtra(Constants.CANCELLED_CALL_INVITE, cancelledCallInvite);
    startService(intent);
}

}

kbagchiGWC commented 4 years ago

Hi @jc-devs, Prateek,

Nice to be re-connected.

How are you keeping track of multiple CallInvites in your app? Our QS demonstrates the simplest use case and assumes one active CallInvite at any given time and keeps track of it here. This will not work correctly if you have multiple call invites. What do you do here?

Can you detail step-by-step what happens?

  1. onCallInvite(..) callback returns the CallInvite object.
  2. RingtoneManagerService starts with ACTION_INCOMING_CALL action
  3. What happens after a similar step like this ?
kbagchiGWC commented 4 years ago

Hi @jc-devs

Were you able to resolve the issue?

jc-devs commented 4 years ago

Hey @kbagchiGWC I don't think this is going in the right direction. The issue is different, we are handling CallInvite's correctly, the issue lies in the fact that when there are way more than notifications in notification tray then our app's priority notification with category call doesn't appear. It still rings the device (we have RingtoneManager added), but there is no way to answer the call - since there is no notification.

jc-devs commented 4 years ago

On another note, we can ask user to open our app to see the incoming call. When someone opens our app, we load a dashboard page (lets call it DashboardActivity.java), while there is another activity where we invoke Twilio libraries called OnCallActivity.java.

How can we check on our DashboardActivity.java that there is an incoming call? {Consider this similar to Whatsapp, where if there is an incoming call and if you open the app directly, it shows a green bar on top which says "Tap to return to call" which you can click to open the Activity and then accept the call}

kbagchiGWC commented 4 years ago

Hi @jc-devs

We have a quick question on the app behavior. Does the behavior differ 20 notifications if the app is in the foreground or in the background?

Thanks.

kbagchiGWC commented 4 years ago

Hi @jc-devs

We did an experiment with the quickstart app.

The work is in this branch.

It seems like you are having issues in your app logic. Can you please elaborate on,

I would suggest to double check the app logic that manages call category list in your app.

jc-devs commented 4 years ago

Hey @kbagchiGWC,

Thanks for trying that out. As mentioned in the start of this thread, this issue is happening on certain Samsung devices and not all, hence I am ruling out code issues here. It works just fine in a wide range of devices even if there are 20 notifications in the tray.

To solve this for Samsung customers who have reported this we are looking forward to this approach - can you help us here?

On another note, we can ask user to open our app to see the incoming call. When someone opens our app, we load a dashboard page (lets call it DashboardActivity.java), while there is another activity where we invoke Twilio libraries called OnCallActivity.java.

How can we check on our DashboardActivity.java that there is an incoming call? {Consider this similar to Whatsapp, where if there is an incoming call and if you open the app directly, it shows a green bar on top which says "Tap to return to call" which you can click to open the Activity and then accept the call}

kbagchiGWC commented 4 years ago

Hi @jc-devs

Can you create a static map to keep track of un-answered call notifications in the IncomingCallNotificationService class? Something like this :

// IncomingCallNotificationService.java
public static HashMap<Integer, CallInvite> unAnsweredCallNotification =  new HashMap();
// Constants.java
public static final String ACTION_APP_RESUMED_SHOW_ACTIVE_NOTIFICATION = "ACTION_APP_RESUMED_SHOW_ACTIVE_NOTIFICATION";

Then when the app is brought to the foreground, inside DashboardActivity.onResume() do something like this:

// DashboardActivity.java 
if (!IncomingCallNotificationService.unAnsweredCallNotification.isEmpty()) {
            Log.d(TAG, "App resumed with pending incoming notification.");
            for (Map.Entry<Integer,CallInvite> entry : IncomingCallNotificationService.unAnsweredCallNotification.entrySet()) {
                Intent acceptIntent = new Intent(getApplicationContext(), IncomingCallNotificationService.class);
                acceptIntent.setAction(Constants.ACTION_APP_RESUMED_SHOW_ACTIVE_NOTIFICATION);
                acceptIntent.putExtra(Constants.INCOMING_CALL_INVITE, entry.getValue());
                acceptIntent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, entry.getKey());
                startService(acceptIntent);
            }
}

Inside IncomingCallNotificationService.onStartCommand() you can do something like this to raise a heads up notification like WhatsApp when the app is in the foreground.

// IncomingCallNotificationService.java
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();

        if (action != null) {
            CallInvite callInvite = intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE);
            int notificationId = intent.getIntExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, 0);
            switch (action) {
                case Constants.ACTION_INCOMING_CALL:
                    handleIncomingCall(callInvite, notificationId);
                    unAnsweredCallNotification.put(notificationId, callInvite);
                    break;
                case Constants.ACTION_APP_RESUMED_SHOW_ACTIVE_NOTIFICATION:
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        startForeground(notificationId, createNotification(callInvite, notificationId, NotificationManager.IMPORTANCE_HIGH));
                    }
                    unAnsweredCallNotification.remove(notificationId);
                    break;
                .....
                case Constants.ACTION_CANCEL_CALL:
                    handleCancelledCall(intent);
                    unAnsweredCallNotification.remove(notificationId);
                    break;
                default:
                    break;
            }
        }
        return START_NOT_STICKY;
    }

Please note, this code is not tested extensively. You need to make further modification to this code, if there are multiple incoming call notifications in the static map and you need your app to replicate WhatsApp behavior.

I am not 100% sure if this is what you needed help with. Please let me know if I misunderstood.

kbagchiGWC commented 4 years ago

Hi @jc-devs

Is your problem resolved following the suggestions above?

kbagchiGWC commented 4 years ago

Closing the ticket due to inactivity. Feel free to open a new issue if you still need help.