From d72648379eefc2c9b2990e8cbfd65ce25400ba78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Fri, 25 Nov 2022 21:49:47 +0100 Subject: [PATCH] Add model for (remote) call participants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clients that modify the model would define the variables using the mutable subclass, while clients that only need to access the model are expected to use the read-only base class. The read-only class provides an observer; as it is expected that the model will be modified from background threads but observed from the main thread the observer can be registered along a handler to be notified on its thread, independently of on which thread the values were set. Currently there does not seem to be a need to observe each value on its own, so the observer is notified in a coarse way when any value changes. Signed-off-by: Daniel Calviño Sánchez --- .../talk/call/CallParticipantModel.java | 164 ++++++++++++++++++ .../call/CallParticipantModelNotifier.java | 86 +++++++++ .../call/MutableCallParticipantModel.java | 67 +++++++ 3 files changed, 317 insertions(+) create mode 100644 app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java create mode 100644 app/src/main/java/com/nextcloud/talk/call/CallParticipantModelNotifier.java create mode 100644 app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java diff --git a/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java new file mode 100644 index 000000000..8c3947824 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java @@ -0,0 +1,164 @@ +/* + * Nextcloud Talk application + * + * @author Daniel Calviño Sánchez + * Copyright (C) 2022 Daniel Calviño Sánchez + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.call; + +import android.os.Handler; + +import org.webrtc.MediaStream; +import org.webrtc.PeerConnection; + +import java.util.Objects; + +/** + * Read-only data model for (remote) call participants. + * + * The received audio and video are available only if the participant is sending them and also has them enabled. + * Before a connection is established it is not known whether audio and video are available or not, so null is returned + * in that case (therefore it should not be autoboxed to a plain boolean without checking that). + * + * Audio and video in screen shares, on the other hand, are always seen as available. + * + * Clients of the model can observe it with CallParticipantModel.Observer to be notified when any value changes. + * Getters called after receiving a notification are guaranteed to provide at least the value that triggered the + * notification, but it may return even a more up to date one (so getting the value again on the following + * notification may return the same value as before). + */ +public class CallParticipantModel { + + public interface Observer { + void onChange(); + } + + protected class Data { + + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + if (Objects.equals(this.value, value)) { + return; + } + + this.value = value; + + callParticipantModelNotifier.notifyChange(); + } + } + + private final CallParticipantModelNotifier callParticipantModelNotifier = new CallParticipantModelNotifier(); + + protected final String sessionId; + + protected Data userId; + protected Data nick; + + protected Data iceConnectionState; + protected Data mediaStream; + protected Data audioAvailable; + protected Data videoAvailable; + + protected Data screenIceConnectionState; + protected Data screenMediaStream; + + public CallParticipantModel(String sessionId) { + this.sessionId = sessionId; + + this.userId = new Data<>(); + this.nick = new Data<>(); + + this.iceConnectionState = new Data<>(); + this.mediaStream = new Data<>(); + this.audioAvailable = new Data<>(); + this.videoAvailable = new Data<>(); + + this.screenIceConnectionState = new Data<>(); + this.screenMediaStream = new Data<>(); + } + + public String getSessionId() { + return sessionId; + } + + public String getUserId() { + return userId.getValue(); + } + + public String getNick() { + return nick.getValue(); + } + + public PeerConnection.IceConnectionState getIceConnectionState() { + return iceConnectionState.getValue(); + } + + public MediaStream getMediaStream() { + return mediaStream.getValue(); + } + + public Boolean isAudioAvailable() { + return audioAvailable.getValue(); + } + + public Boolean isVideoAvailable() { + return videoAvailable.getValue(); + } + + public PeerConnection.IceConnectionState getScreenIceConnectionState() { + return screenIceConnectionState.getValue(); + } + + public MediaStream getScreenMediaStream() { + return screenMediaStream.getValue(); + } + + /** + * Adds an Observer to be notified when any value changes. + * + * @param observer the Observer + * @see CallParticipantModel#addObserver(Observer, Handler) + */ + public void addObserver(Observer observer) { + addObserver(observer, null); + } + + /** + * Adds an observer to be notified when any value changes. + * + * The observer will be notified on the thread associated to the given handler. If no handler is given the + * observer will be immediately notified on the same thread that changed the value; the observer will be + * immediately notified too if the thread of the handler is the same thread that changed the value. + * + * An observer is expected to be added only once. If the same observer is added again it will be notified just + * once on the thread of the last handler. + * + * @param observer the Observer + * @param handler a Handler for the thread to be notified on + */ + public void addObserver(Observer observer, Handler handler) { + callParticipantModelNotifier.addObserver(observer, handler); + } + + public void removeObserver(Observer observer) { + callParticipantModelNotifier.removeObserver(observer); + } +} diff --git a/app/src/main/java/com/nextcloud/talk/call/CallParticipantModelNotifier.java b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModelNotifier.java new file mode 100644 index 000000000..ddf30c1d7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModelNotifier.java @@ -0,0 +1,86 @@ +/* + * Nextcloud Talk application + * + * @author Daniel Calviño Sánchez + * Copyright (C) 2022 Daniel Calviño Sánchez + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.call; + +import android.os.Handler; +import android.os.Looper; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Helper class to register and notify CallParticipantModel.Observers. + * + * This class is only meant for internal use by CallParticipantModel; observers must register themselves against a + * CallParticipantModel rather than against a CallParticipantModelNotifier. + */ +class CallParticipantModelNotifier { + + /** + * Helper class to associate a CallParticipantModel.Observer with a Handler. + */ + private static class CallParticipantModelObserverOn { + public final CallParticipantModel.Observer observer; + public final Handler handler; + + private CallParticipantModelObserverOn(CallParticipantModel.Observer observer, Handler handler) { + this.observer = observer; + this.handler = handler; + } + } + + private final List callParticipantModelObserversOn = new ArrayList<>(); + + public synchronized void addObserver(CallParticipantModel.Observer observer, Handler handler) { + if (observer == null) { + throw new IllegalArgumentException("CallParticipantModel.Observer can not be null"); + } + + removeObserver(observer); + + callParticipantModelObserversOn.add(new CallParticipantModelObserverOn(observer, handler)); + } + + public synchronized void removeObserver(CallParticipantModel.Observer observer) { + Iterator it = callParticipantModelObserversOn.iterator(); + while (it.hasNext()) { + CallParticipantModelObserverOn observerOn = it.next(); + + if (observerOn.observer == observer) { + it.remove(); + + return; + } + } + } + + public synchronized void notifyChange() { + for (CallParticipantModelObserverOn observerOn : new ArrayList<>(callParticipantModelObserversOn)) { + if (observerOn.handler == null || observerOn.handler.getLooper() == Looper.myLooper()) { + observerOn.observer.onChange(); + } else { + observerOn.handler.post(() -> { + observerOn.observer.onChange(); + }); + } + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java b/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java new file mode 100644 index 000000000..4023bd296 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java @@ -0,0 +1,67 @@ +/* + * Nextcloud Talk application + * + * @author Daniel Calviño Sánchez + * Copyright (C) 2022 Daniel Calviño Sánchez + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.call; + +import org.webrtc.MediaStream; +import org.webrtc.PeerConnection; + +/** + * Mutable data model for (remote) call participants. + * + * There is no synchronization when setting the values; if needed, it should be handled by the clients of the model. + */ +public class MutableCallParticipantModel extends CallParticipantModel { + + public MutableCallParticipantModel(String sessionId) { + super(sessionId); + } + + public void setUserId(String userId) { + this.userId.setValue(userId); + } + + public void setNick(String nick) { + this.nick.setValue(nick); + } + + public void setIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) { + this.iceConnectionState.setValue(iceConnectionState); + } + + public void setMediaStream(MediaStream mediaStream) { + this.mediaStream.setValue(mediaStream); + } + + public void setAudioAvailable(Boolean audioAvailable) { + this.audioAvailable.setValue(audioAvailable); + } + + public void setVideoAvailable(Boolean videoAvailable) { + this.videoAvailable.setValue(videoAvailable); + } + + public void setScreenIceConnectionState(PeerConnection.IceConnectionState screenIceConnectionState) { + this.screenIceConnectionState.setValue(screenIceConnectionState); + } + + public void setScreenMediaStream(MediaStream screenMediaStream) { + this.screenMediaStream.setValue(screenMediaStream); + } +}