jitsi-meet
jitsi-meet copied to clipboard
fix: use Gson to stringify intent data in BroadcastEvent
Fixes #9676
Use Gson stringifier in BroadcastEvent, instead of toString(), for stringifying the intent extra data. This allows the transfer of hashMap data.
Hi, thanks for your contribution! If you haven't already done so, could you please make sure you sign our CLA (https://jitsi.org/icla for individuals and https://jitsi.org/ccla for corporations)? We would unfortunately be unable to merge your patch unless we have that piece :(.
Hi! I have the same problem here. One question: Shouldn't you first check that data is a HashMap before serializing to JSON instead of calling toString?
Ping @tmoldovan8x8
Hi @ernest3rd @AlvaroBro, Could you please describe the situation when you want to send a map of maps in an event? Maybe it's a use case we could add to the sdk.
Hi @tmoldovan8x8
I really don't know all the possible events and data associated to them so I couldn't tell.
I'm just encountering this particular case, where the PARTICIPANTS_INFO_RETRIEVED event's data is a HashMap and is being serialized with toString (BroadcastEvent.java):
public Intent buildIntent() {
if (type != null && type.action != null) {
Intent intent = new Intent(type.action);
for (String key : this.data.keySet()) {
try {
intent.putExtra(key, this.data.get(key).toString());
} catch (Exception e) {
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
}
}
return intent;
}
return null;
}
But then it is being deserialized in ParticipantsService.java, and it is expecting to deserialize a JSON string, which of course fails:
@Override
public void onReceive(Context context, Intent intent) {
BroadcastEvent event = new BroadcastEvent(intent);
switch (event.getType()) {
case PARTICIPANTS_INFO_RETRIEVED:
try {
List<ParticipantInfo> participantInfoList = new Gson().fromJson(
event.getData().get("participantsInfo").toString(),
new TypeToken<ArrayList<ParticipantInfo>>() {
}.getType());
ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();
if (participantsInfoCallback != null) {
participantsInfoCallback.onReceived(participantInfoList);
this.participantsInfoCallbackMap.remove(participantsInfoCallback);
}
} catch (Exception e) {
JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
}
break;
}
}
Because of this, the ParticipantsService wont work.
@ernest3rd proposed a fix which consists in serializing the event data (in buildIntent()) as a JSON string, but as BroadcastEvent is a generic class and I asume that it might be used for other cases, I was just asking if that fix would broke other events.
I don't know the whole project code enough to make an statement, just pointing it out.
Me, I will try to fix this problem by checking the event type before serializing data, and only if it is of type PARTICIPANTS_INFO_RETRIEVED, I will serialize it as a JSON string as @ernest3rd proposed.
Regards!
To my understanding as everything is already serialized to a string it should not break anything. Gson serialization outputs strings as strings so it should work the same way for those cases. But I could be wrong.
Cheers!
Hey @AlvaroBro, can you please send us the response you are getting in the PARTICIPANTS-INFO-RETRIEVED event? The fix here can definitely work, but it might hide another problem that also cause iOS to fail sometimes. The data should contain a participantsInfo key with an array and a requestId with a string value.
@tmoldovan8x8 , you mean what is the value of this.data.get(key)
in buildIntent()
method (se above)?
The value there is correct, it is a HashMap
that contains the keys that you mention. The problem is that it is serialized using toString, which makes it impossible to deserialize it as if it were JSON.
I implemented the same solution as @ernest3rd but with a bit more of sanity checking, just in case. With this change, in Android, it works 100% of the times.
public Intent buildIntent() {
if (type != null && type.action != null) {
Intent intent = new Intent(type.action);
for (String key : this.data.keySet()) {
try {
switch (getType()) {
case PARTICIPANTS_INFO_RETRIEVED:
if ("participantsInfo".equals(key)) {
intent.putExtra(key, new Gson().toJson(this.data.get(key)));
break;
}
// Intended fall down
default:
intent.putExtra(key, this.data.get(key).toString());
break;
}
} catch (Exception e) {
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
}
}
return intent;
}
return null;
}
Now I have a problem in iOS, where I'm seeing it fail sometimes (retrieveParticipantsInfo's
completion handler not called)
@AlvaroBro you mean what is the value of this.data.get(key) in buildIntent()
yes, the data that is received from the native side. Actually can you paste the entire content of this.data. Normally 'participantsInfo' should be an array of ParticipantsInfo, which does work with toString, so we're trying to figure out in what situation there is a HashMap there.
@tmoldovan8x8 if you call toString on a HashMap, it surely works in the sense that it will return a String object, but it won't be a JSON string object. That is the problem. The other side expects a JSON string. The data from the native side is always correct at least when using the Android sdk.
@AlvaroBro yes. What I was trying to figure out is in what situation this.data.get(key) is a hashmap and causes that problem. Normally it should be an array, which works fine.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I also encountered this problem, because there are spaces in the name, it cannot be parsed, and the object is converted to a string without double quotation marks
Mistake:
org.json.JSONException: Unterminated object at character 39 of
[{participantId=bc79ed36, name=dddd &*^&(&#(, role=none, isLocal=true}]
I used @AlvaroBro solution and it works fine
and
@Override
public void onReceive(Context context, Intent intent) {
BroadcastEvent event = new BroadcastEvent(intent);
switch (event.getType()) {
case PARTICIPANTS_INFO_RETRIEVED:
try {
JSONArray participantsInfoJSONArray= new JSONArray(event.getData().get("participantsInfo").toString());
List<ParticipantInfo> participantInfoList = new Gson().fromJson(
participantsInfoJSONArray.toString(),
new TypeToken<ArrayList<ParticipantInfo>>() {
}.getType());
ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();
if (participantsInfoCallback != null) {
participantsInfoCallback.onReceived(participantInfoList);
this.participantsInfoCallbackMap.remove(participantsInfoCallback);
}
} catch (Exception e) {
JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
}
break;
}
}
Solve:
[{"participantId":"3febda6f","name":"dddd &*^&(&#(","role":"none","isLocal":true}]
Ping @tmoldovan8x8
@TingHuang222324 i have the same problem, when the name have spaces and if I fill the avatar with a url it also fails to parse.
This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.