ClassNotFoundException com.twilio.voice.CallInvite when reading ConnectionService Bundle extras in onCreateIncomingConnection
I'm upgrading a Flutter library to use a Twilio Voice integrated with a ConnectionService for native call management. To accomplish this, one has to extend a ConnectionService
(inherits from a Service
with associated communication methods, i.e. Intent
s)
Background & Context
To receive incoming call (via a notification manager like FCM), one needs to notify the ConnectionService
of an incoming call using
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
Log.d(TAG, "onCreateIncomingConnection")'
// ... code goes here
// return new Connection(...)
}
With this onCreateIncomingConnection
, it will ask the native 'Phone Call' app to show an incoming call with parameters you provide, e.g. showing the incoming caller number with Connection.setAddress
This additional information can be passed into the ConnectionService
by adding them to an extras Bundle
with the Key TelecomManager.EXTRA_INCOMING_CALL_EXTRAS (for incoming calls) which is then added to a new Bundle
e.g.
private fun handleFCMCallInvite(callInvite: CallInvite, notificationId: Int) {
// Get telecom manager
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
// Get PhoneAccountHandle
val caller = callInvite.from!!.toString()
val componentName = ComponentName(applicationContext.packageName, TwilioVoiceConnectionService::class.java.name)
val phoneAccountHandle = PhoneAccountHandle(componentName, caller)
// Create my Bundle containing information e.g. notificationId and callInvite
val myBundle = Bundle()
myBundle.putInt(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
myBundle.putParcelable(Constants.INCOMING_CALL_INVITE, callInvite)
// Add new incoming call to the telecom manager
telecomManager.addNewIncomingCall(phoneAccountHandle, Bundle().apply {
putBundle(EXTRA_INCOMING_CALL_EXTRAS, myBundle)
putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
})
}
Problem
The issue comes in when trying to unparcel()
the CallInvite
from Bundled extras in the ConnectionService
's onCreateIncomingConnection implementation, see as follows:
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
Log.d(TAG, "onCreateIncomingConnection")
val connection: Connection = VoipConnection(applicationContext)
connection.extras = request?.extras
var ci: CallInvite? = null
val myBundle: Bundle? = connection.extras.getBundle(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
if(myBundle != null) {
Log.d(TAG, "onCreateIncomingConnection: myBundle is not null")
/// --------------------------------------------------------
/// This next line throws the ClassNotFoundException occurs
/// --------------------------------------------------------
if (myBundle.containsKey(Constants.INCOMING_CALL_INVITE) ) {
Log.d(TAG, "onCreateIncomingConnection: myBundle contains INCOMING_CALL_INVITE")
ci = myBundle.getParcelable(Constants.INCOMING_CALL_INVITE)
} else {
Log.d(TAG, "onCreateIncomingConnection: myBundle does not contain INCOMING_CALL_INVITE")
}
} else {
Log.d(TAG, "onCreateIncomingConnection: myBundle is null")
}
...
}
As shown in the snippet above, when calling myBundle.containsKey()
with a valid key any time I have a CallInvite object bundled in. If I only pass in e.g. notificationId
, contains and accessors work as intended.
When including the CallInvite
in the bundle, I get the following exception:
*Note: CallInvite
implements Parcelable
Class not found when unmarshalling: com.twilio.voice.CallInvite
java.lang.ClassNotFoundException: com.twilio.voice.CallInvite
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:454)
at android.os.Parcel.readParcelableCreator(Parcel.java:3403)
at android.os.Parcel.readParcelable(Parcel.java:3337)
at android.os.Parcel.readValue(Parcel.java:3239)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3636)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.BaseBundle.containsKey(BaseBundle.java:516)
at com.twilio.twilio_voice.connectionservice.TwilioVoiceConnectionService.onCreateIncomingConnection(TwilioVoiceConnectionService.kt:43)
at android.telecom.ConnectionService.createConnection(ConnectionService.java:2061)
at android.telecom.ConnectionService.access$400(ConnectionService.java:96)
at android.telecom.ConnectionService$2$1.loggedRun(ConnectionService.java:914)
at android.telecom.Logging.Runnable$1.run(Runnable.java:37)
at android.telecom.ConnectionService.onAccountsInitialized(ConnectionService.java:3272)
at android.telecom.ConnectionService.access$5000(ConnectionService.java:96)
at android.telecom.ConnectionService$5$1.loggedRun(ConnectionService.java:2577)
at android.telecom.Logging.Runnable$1.run(Runnable.java:37)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Caused by: java.lang.ClassNotFoundException: com.twilio.voice.CallInvite
at java.lang.Class.classForName(Native Method)
at java.lang.BootClassLoader.findClass(ClassLoader.java:1358)
at java.lang.BootClassLoader.loadClass(ClassLoader.java:1418)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:454)
at android.os.Parcel.readParcelableCreator(Parcel.java:3403)
at android.os.Parcel.readParcelable(Parcel.java:3337)
at android.os.Parcel.readValue(Parcel.java:3239)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3636)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.BaseBundle.containsKey(BaseBundle.java:516)
at com.twilio.twilio_voice.connectionservice.TwilioVoiceConnectionService.onCreateIncomingConnection(TwilioVoiceConnectionService.kt:43)
at android.telecom.ConnectionService.createConnection(ConnectionService.java:2061)
at android.telecom.ConnectionService.access$400(ConnectionService.java:96)
at android.telecom.ConnectionService$2$1.loggedRun(ConnectionService.java:914)
at android.telecom.Logging.Runnable$1.run(Runnable.java:37)
at android.telecom.ConnectionService.onAccountsInitialized(ConnectionService.java:3272)
at android.telecom.ConnectionService.access$5000(ConnectionService.java:96)
at android.telecom.ConnectionService$5$1.loggedRun(ConnectionService.java:2577)
at android.telecom.Logging.Runnable$1.run(Runnable.java:37)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
Libraries, etc
build.gradle
(module)
buildscript {
ext.kotlin_version = '1.8.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
...
android {
compileSdk 33
...
}
...
dependencies {
...
implementation("com.twilio:voice-android:6.2.1")
implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
...
}
UPDATE 1
Using the last working solution (Java), I resolved to parcelling and unparcelling immediately to emulate what's happening in the background, seems there might be a problem here.
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)
Comments
Post a Comment