Signal-Android/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java

215 lines
7.9 KiB
Java

package org.thoughtcrime.securesms.components.webrtc;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
public class WebRtcCallViewModel extends ViewModel {
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
private boolean canDisplayTooltipIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
private long callConnectedTime = -1;
private Handler elapsedTimeHandler = new Handler(Looper.getMainLooper());
private boolean answerWithVideoAvailable = false;
private Runnable elapsedTimeRunnable = this::handleTick;
private boolean canEnterPipMode = false;
private final WebRtcCallRepository repository = new WebRtcCallRepository();
public LiveData<Boolean> getMicrophoneEnabled() {
return Transformations.distinctUntilChanged(microphoneEnabled);
}
public LiveData<WebRtcControls> getWebRtcControls() {
return realWebRtcControls;
}
public LiveRecipient getRecipient() {
return liveRecipient.getValue();
}
public void setRecipient(@NonNull Recipient recipient) {
liveRecipient.setValue(recipient.live());
}
public LiveData<Event> getEvents() {
return events;
}
public LiveData<Long> getCallTime() {
return Transformations.map(elapsed, timeInCall -> callConnectedTime == -1 ? -1 : timeInCall);
}
public LiveData<CallParticipantsState> getCallParticipantsState() {
return participantsState;
}
public boolean canEnterPipMode() {
return canEnterPipMode;
}
public boolean isAnswerWithVideoAvailable() {
return answerWithVideoAvailable;
}
@MainThread
public void setIsInPipMode(boolean isInPipMode) {
this.isInPipMode.setValue(isInPipMode);
//noinspection ConstantConditions
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
}
@MainThread
public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) {
//noinspection ConstantConditions
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), page));
}
public void onDismissedVideoTooltip() {
canDisplayTooltipIfNeeded = false;
}
@MainThread
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) {
canEnterPipMode = webRtcViewModel.getState() != WebRtcViewModel.State.CALL_PRE_JOIN;
CallParticipant localParticipant = webRtcViewModel.getLocalParticipant();
microphoneEnabled.setValue(localParticipant.isMicrophoneEnabled());
//noinspection ConstantConditions
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), webRtcViewModel, enableVideo));
updateWebRtcControls(webRtcViewModel.getState(),
localParticipant.getCameraState().isEnabled(),
webRtcViewModel.isRemoteVideoEnabled(),
webRtcViewModel.isRemoteVideoOffer(),
localParticipant.isMoreThanOneCameraAvailable(),
webRtcViewModel.isBluetoothAvailable(),
repository.getAudioOutput());
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
callConnectedTime = webRtcViewModel.getCallConnectedTime();
startTimer();
} else if (webRtcViewModel.getState() != WebRtcViewModel.State.CALL_CONNECTED) {
cancelTimer();
callConnectedTime = -1;
}
if (localParticipant.getCameraState().isEnabled()) {
canDisplayTooltipIfNeeded = false;
hasEnabledLocalVideo = true;
events.setValue(Event.DISMISS_VIDEO_TOOLTIP);
}
// If remote video is enabled and we a) haven't shown our video and b) have not dismissed the popup
if (canDisplayTooltipIfNeeded && webRtcViewModel.isRemoteVideoEnabled() && !hasEnabledLocalVideo) {
canDisplayTooltipIfNeeded = false;
events.setValue(Event.SHOW_VIDEO_TOOLTIP);
}
}
private void updateWebRtcControls(@NonNull WebRtcViewModel.State state,
boolean isLocalVideoEnabled,
boolean isRemoteVideoEnabled,
boolean isRemoteVideoOffer,
boolean isMoreThanOneCameraAvailable,
boolean isBluetoothAvailable,
@NonNull WebRtcAudioOutput audioOutput)
{
final WebRtcControls.CallState callState;
switch (state) {
case CALL_PRE_JOIN:
callState = WebRtcControls.CallState.PRE_JOIN;
break;
case CALL_INCOMING:
callState = WebRtcControls.CallState.INCOMING;
answerWithVideoAvailable = isRemoteVideoOffer;
break;
case CALL_OUTGOING:
case CALL_RINGING:
callState = WebRtcControls.CallState.OUTGOING;
break;
case CALL_ACCEPTED_ELSEWHERE:
case CALL_DECLINED_ELSEWHERE:
case CALL_ONGOING_ELSEWHERE:
case CALL_NEEDS_PERMISSION:
case CALL_BUSY:
case CALL_DISCONNECTED:
callState = WebRtcControls.CallState.ENDING;
break;
default:
callState = WebRtcControls.CallState.ONGOING;
}
webRtcControls.setValue(new WebRtcControls(isLocalVideoEnabled,
isRemoteVideoEnabled || isRemoteVideoOffer,
isMoreThanOneCameraAvailable,
isBluetoothAvailable,
Boolean.TRUE.equals(isInPipMode.getValue()),
callState,
audioOutput));
}
private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) {
return isInPipMode ? WebRtcControls.PIP : controls;
}
private void startTimer() {
cancelTimer();
elapsedTimeHandler.post(elapsedTimeRunnable);
}
private void handleTick() {
if (callConnectedTime == -1) {
return;
}
long newValue = (System.currentTimeMillis() - callConnectedTime) / 1000;
elapsed.postValue(newValue);
elapsedTimeHandler.postDelayed(elapsedTimeRunnable, 1000);
}
private void cancelTimer() {
elapsedTimeHandler.removeCallbacks(elapsedTimeRunnable);
}
@Override
protected void onCleared() {
super.onCleared();
cancelTimer();
}
public enum Event {
SHOW_VIDEO_TOOLTIP,
DISMISS_VIDEO_TOOLTIP
}
}