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_INVITE extra is null when Incoming call notification in clicked. #370

Closed Shivamdhuria closed 3 years ago

Shivamdhuria commented 4 years ago

Description

If the notification for the incoming call is clicked, it takes you to the VoiceActivity Screen where the intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE) is turning out to be null, and the callInvite object is lost.

Steps to Reproduce

  1. Call to the device, while the screen in unlocked and the App is in background.
  2. Click on the incoming notification.
  3. It takes you the VoiceActivity Screen where the callInvite extra on the pending intent is null. .

    Code

 private void handleIncomingCall(CallInvite callInvite, int notificationId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            setCallInProgressNotification(callInvite, notificationId);
        }
        sendCallInviteToActivity(callInvite, notificationId);
    }
 @TargetApi(Build.VERSION_CODES.O)
    private void setCallInProgressNotification(CallInvite callInvite, int notificationId) {
        if (isAppVisible()) {
            Log.i(TAG, "setCallInProgressNotification - app is visible.");
            startForeground(notificationId, createNotification(callInvite, notificationId, NotificationManager.IMPORTANCE_LOW));
        } else {
            Log.i(TAG, "setCallInProgressNotification - app is NOT visible.");
            startForeground(notificationId, createNotification(callInvite, notificationId, NotificationManager.IMPORTANCE_HIGH));
        }
    }
    private Notification createNotification(CallInvite callInvite, int notificationId, int channelImportance) {
        Intent intent = new Intent(this, VoiceActivity.class);
        intent.setAction(Constants.ACTION_INCOMING_CALL_NOTIFICATION);
        intent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId);
        intent.putExtra(Constants.INCOMING_CALL_INVITE, callInvite);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        /*
         * Pass the notification id and call sid to use as an identifier to cancel the
         * notification later
         */
        Bundle extras = new Bundle();
        extras.putString(Constants.CALL_SID_KEY, callInvite.getCallSid());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return buildNotification(callInvite.getFrom() + " is calling.",
                    pendingIntent,
                    extras,
                    callInvite,
                    notificationId,
                    createChannel(channelImportance));
        } else {
            //noinspection deprecation
            return new NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.ic_call_end_white_24dp)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText(callInvite.getFrom() + " is calling.")
                    .setAutoCancel(true)
                    .setExtras(extras)
                    .setContentIntent(pendingIntent)
                    .setGroup("test_app_notification")
                    .setColor(Color.rgb(214, 10, 37)).build();
        }
    }

In VoiceActivity

private void handleIncomingCallIntent(Intent intent) {
        if (intent != null && intent.getAction() != null) {
            //Action is Successfully received
            String action = intent.getAction();
           // The below parcelable extra is null
            activeCallInvite = intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE);
            activeCallNotificationId = intent.getIntExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, 0);

            switch (action) {
                case Constants.ACTION_INCOMING_CALL:
                    handleIncomingCall();
                    break;
                case Constants.ACTION_INCOMING_CALL_NOTIFICATION:
                    showIncomingCallDialog();``

Expected Behavior

The Extra shouldn't be null and incoming call dialog should be shown.

Actual Behavior

Nothing Happens when you reach the Voice Activity screen after clicking the Notification

Reproduces How Often

100%

Voice Android SDK

[5.4.0]

OS Version

[Android 10]

Device Model

[All Samsung phones and I guess all other phones too (haven't confirmed)]

Note

The Reject and Accept button from the notifications work all the time, and even if I pass more string extras in the pending intent , I am unable to retrieve them in the activity as well. However intent.getData() in the activity works fine along with intent.getAction.

Additional Information

If I do NOT do pass this in pending intent intent.putExtra(Constants.INCOMING_CALL_INVITE, callInvite); while passing any other extras, they are received properly in the activity.

Shivamdhuria commented 4 years ago

Any update on this?

kbagchiGWC commented 4 years ago

@Shivamdhuria

Can you check the value of the call invite at this line ?

You can add few break points at here, here and here to check where the value is being altered.

Shivamdhuria commented 4 years ago

I have checked the CallInvite value at all the above points plus here too. Again, i'd like to clarify that problem only exists when the phone is unlocked and then you "click" the incoming call notification. My guess would be that something goes wrong while creating the pending intent. I can verify that up until that point callInvite value is never null. I have also literally copy/pasted code from this repo to my project to see If i did anything wrong at some point but I get same results. Is this not reproducible for you guys? @kbagchiGWC

kbagchiGWC commented 4 years ago

@Shivamdhuria

We have tried the same use case - incoming call when the phone is unlocked and app in the background and cannot reproduce. Can you reproduce the issue using the quickstart as is?

Shivamdhuria commented 4 years ago

Sure,That sounds great. I'll just fork this, add some custom key logic (from my backend) and try again. Will get back on this. Thanks!

MihailovDev commented 4 years ago

Hey, I have the same problem. I just copied everything in the quickstart app and pasted it in my project. My project is in kotlin so i automatically reformatted the code to kotlin using the android studio IDE. Any news?

Shivamdhuria commented 4 years ago

@MihailovDev @kbagchiGWC For me, I tried to use the converted to kotlint code as well as copying the all the files to my project (as is in java)too. I face this issue for both scenarios.

I currently have a fixed this issue by using this

object ParcelableUtil {
    fun marshall(parceable: Parcelable): ByteArray {
        val parcel = Parcel.obtain()
        parceable.writeToParcel(parcel, 0)
        val bytes = parcel.marshall()
        parcel.recycle()
        return bytes
    }

    fun unmarshall(bytes: ByteArray): Parcel {
        val parcel = Parcel.obtain()
        parcel.unmarshall(bytes, 0, bytes.size)
        parcel.setDataPosition(0) // This is extremely important!
        return parcel
    }

    fun <T> unmarshall(bytes: ByteArray, creator: Parcelable.Creator<T>): T {
        val parcel = unmarshall(bytes)
        val result = creator.createFromParcel(parcel)
        parcel.recycle()
        return result
    }
}

Converting call Invite to bytes using -

` val bytes = marshall(callInvite)`
  intent.putExtra(CALL_INVITE_BYTES, bytes)

And in handleIncomingCallIntent in Activity do -

  val bytes = intent.getByteArrayExtra(CALL_INVITE_BYTES)
            bytes?.let {
                val parcel = unmarshall(bytes)
                activeCallInvite = CallInvite.CREATOR.createFromParcel(parcel) as CallInvite
            }

This seem to work for me for most phones, except Samsung m21 for some reason.

Currently, for the project I am working right now, this issue is on non priority so I'll try to see this issue later, when I'll try to use the same exact code(quickstart) for my backend. But I'll be glad if you guys can see what might be causing the issue here as this is happening for another project too which my teammates are working on.

kbagchiGWC commented 4 years ago

@Shivamdhuria

Following up on this. Did you get a chance to try the suggestions?

Thanks.

kbagchiGWC commented 4 years ago

@MihailovDev - are you able to reproduce the issue by using the QS as is?

Shivamdhuria commented 4 years ago

@kbagchiGWC ,will start working on it next week.

This should probably rule out if possibility of a bug in this code.

Let me know you you have any more suggestions.

Shivamdhuria commented 4 years ago

Hi @kbagchiGWC @MihailovDev , I forked this repo , changed the TWILIO_ACCESS_TOKEN_SERVER_URL, added my .json file and just added few logs. https://github.com/Shivamdhuria/voice-quickstart-android

The code is clearly not working - Steps to reproduce

The value of activeCallInvite is null in handleIncomingCallIntent()

private void handleIncomingCallIntent (Intent intent) { intent Intent { act=ACTION_INCOMING_CALL _NOTIFICATION cmp=com twilio v

Because of this the value of activeCallInvite in showIncomingCallDialog() is null as well.

private void showIncomingCallDialog() {

I tested this(QS)on a Samsung galaxy M20(Android 10), but I have observed this issue on all the devices I tried my project on. Let me know if I am doing anything wrong.

Brajendra commented 4 years ago

HI @Shivamdhuria @kbagchiGWC Are you able to find any solution? I am also facing same issue, Getting CallInvite object null in my Oppo F9 Pro Device, As I think there is chance size limit exceeded when we are passing CallInvite object from Notification using Extras.

kbagchiGWC commented 4 years ago

@Shivamdhuria - thanks for the screenshots and the details. We will investigate and get back to you.

Shivamdhuria commented 3 years ago

Hey @kbagchiGWC , any updates on this? Were you guys able to reproduce/resolve this issue?

kbagchiGWC commented 3 years ago

@Shivamdhuria

We have done extensive testing but not able to reproduce using the devices we currently have. We don't have Samsung galaxy M20(Android 10) with us. Can you list all devices where the issue is reproducible?

Shivamdhuria commented 3 years ago

@kbagchiGWC Hey, I tested all the devices I could get hold of today. The issue is happening in Samsung M20 and Samsung M21 only. I also tried Samsung M31 and Pixel 3a too where I could not reproduce the issue. Now I understand that this probably happening due to OEMs fault but any ideas about why this might happen?

kbagchiGWC commented 3 years ago

@Shivamdhuria

I agree it seems to be a device specific issue. Do you see anything useful in the log?

Shivamdhuria commented 3 years ago

@kbagchiGWC Not really. The only other information that might help is that

intent.putExtra(Constants.INCOMING_CALL_INVITE, callInvite);
intent.putExtra(Constants.SOME_STRING, "random string");

I do not receive any of them in the activity.

intent.putExtra(Constants.SOME_STRING, "random string");

I am able to receive it in the activity.

Again the issue is only for M20,M21 Samsung

cybex-dev commented 1 year ago

@kbagchiGWC @Shivamdhuria not entirely the same, but somewhat related (Samsung A21s & Samsung Note 10+). I seem to be running into a similar problem: screen on & app in foreground, on Call Invite received (via FCM), I handleInvite as follows:

public class VoiceFirebaseMessagingService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(final RemoteMessage remoteMessage) {
        // ... valid message
        handleInvite(callInvite, notificationId);
        //...
    }

    private void handleInvite(CallInvite callInvite, int notificationId) {
        Parcel p = Parcel.obtain();
        p.writeParcelable(callInvite, 0);
        ClassLoader classLoader = getApplicationContext().getClassLoader();
        CallInvite ci = p.readParcelable(classLoader); // <----------------- ci is always null
        if(ci != null){
            Log.d(TAG, "handleInvite: " + ci.getCallSid());
        } else {
            Log.d(TAG, "handleInvite: null");
        }
    }

}

The same occurs on with Kotlin, though I get a ClassNotFoundException for CallInvite.

So far, I've traced the issue to Parcel.readParcelableCreator(@Nullable ClassLoader) (for Android 11, API 30)

public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
        String name = readString();
        if (name == null) {
            return null;
        }
        Parcelable.Creator<?> creator;

name is always null, thus returns null when calling Parcel.readParcelable.

This occurs on both Android 11 & 12 (API 30, 31)