mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 13:26:12 +00:00
Implement layout changes to new call screen UX.
This commit is contained in:
@@ -203,8 +203,6 @@ public class WebRtcCallActivity extends AppCompatActivity {
|
||||
viewModel = ViewModelProviders.of(this).get(WebRtcCallViewModel.class);
|
||||
viewModel.setIsInPipMode(isInPipMode());
|
||||
viewModel.getRemoteVideoEnabled().observe(this,callScreen::setRemoteVideoEnabled);
|
||||
viewModel.getBluetoothEnabled().observe(this, callScreen::setBluetoothEnabled);
|
||||
viewModel.getAudioOutput().observe(this, callScreen::setAudioOutput);
|
||||
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
||||
viewModel.getCameraDirection().observe(this, callScreen::setCameraDirection);
|
||||
viewModel.getLocalRenderState().observe(this, callScreen::setLocalRenderState);
|
||||
@@ -212,7 +210,6 @@ public class WebRtcCallActivity extends AppCompatActivity {
|
||||
viewModel.getEvents().observe(this, this::handleViewModelEvent);
|
||||
viewModel.getCallTime().observe(this, this::handleCallTime);
|
||||
viewModel.displaySquareCallCard().observe(this, callScreen::showCallCard);
|
||||
viewModel.isMoreThanOneCameraAvailable().observe(this, callScreen::showCameraToggleButton);
|
||||
}
|
||||
|
||||
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||
@@ -253,17 +250,23 @@ public class WebRtcCallActivity extends AppCompatActivity {
|
||||
callScreen.setStatus(getString(R.string.WebRtcCallActivity__signal_s, ellapsedTimeFormatter.toString()));
|
||||
}
|
||||
|
||||
private void handleSetAudioSpeaker(boolean enabled) {
|
||||
private void handleSetAudioHandset() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, enabled);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void handleSetAudioBluetooth(boolean enabled) {
|
||||
private void handleSetAudioSpeaker() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, true);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void handleSetAudioBluetooth() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, enabled);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, true);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@@ -546,13 +549,13 @@ public class WebRtcCallActivity extends AppCompatActivity {
|
||||
public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) {
|
||||
switch (audioOutput) {
|
||||
case HANDSET:
|
||||
handleSetAudioSpeaker(false);
|
||||
handleSetAudioHandset();
|
||||
break;
|
||||
case HEADSET:
|
||||
handleSetAudioBluetooth(true);
|
||||
handleSetAudioBluetooth();
|
||||
break;
|
||||
case SPEAKER:
|
||||
handleSetAudioSpeaker(true);
|
||||
handleSetAudioSpeaker();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown output: " + audioOutput);
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@@ -12,26 +18,35 @@ import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.AudioOutputViewHolder> {
|
||||
final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.ViewHolder> {
|
||||
|
||||
private final Consumer<WebRtcAudioOutput> consumer;
|
||||
private final List<WebRtcAudioOutput> audioOutputs;
|
||||
private final OnAudioOutputChangedListener onAudioOutputChangedListener;
|
||||
private final List<WebRtcAudioOutput> audioOutputs;
|
||||
|
||||
AudioOutputAdapter(@NonNull Consumer<WebRtcAudioOutput> consumer, @NonNull List<WebRtcAudioOutput> audioOutputs) {
|
||||
this.audioOutputs = audioOutputs;
|
||||
this.consumer = consumer;
|
||||
private WebRtcAudioOutput selected;
|
||||
|
||||
AudioOutputAdapter(@NonNull OnAudioOutputChangedListener onAudioOutputChangedListener,
|
||||
@NonNull List<WebRtcAudioOutput> audioOutputs) {
|
||||
this.audioOutputs = audioOutputs;
|
||||
this.onAudioOutputChangedListener = onAudioOutputChangedListener;
|
||||
}
|
||||
|
||||
public void setSelectedOutput(@NonNull WebRtcAudioOutput selected) {
|
||||
this.selected = selected;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull AudioOutputViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new AudioOutputViewHolder((TextView) LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_output_adapter_item, parent, false), consumer);
|
||||
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_output_adapter_radio_item, parent, false);
|
||||
|
||||
return new ViewHolder(view, this::handlePositionSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AudioOutputViewHolder holder, int position) {
|
||||
WebRtcAudioOutput audioOutput = audioOutputs.get(position);
|
||||
holder.view.setText(audioOutput.getLabelRes());
|
||||
holder.view.setCompoundDrawablesRelativeWithIntrinsicBounds(audioOutput.getIconRes(), 0, 0, 0);
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.bind(audioOutputs.get(position), selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,21 +54,46 @@ final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.A
|
||||
return audioOutputs.size();
|
||||
}
|
||||
|
||||
final static class AudioOutputViewHolder extends RecyclerView.ViewHolder {
|
||||
private void handlePositionSelected(int position) {
|
||||
WebRtcAudioOutput mode = audioOutputs.get(position);
|
||||
|
||||
private final TextView view;
|
||||
|
||||
AudioOutputViewHolder(@NonNull TextView itemView, @NonNull Consumer<WebRtcAudioOutput> consumer) {
|
||||
super(itemView);
|
||||
|
||||
view = itemView;
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
consumer.accept(WebRtcAudioOutput.values()[getAdapterPosition()]);
|
||||
}
|
||||
});
|
||||
if (mode != selected) {
|
||||
setSelectedOutput(mode);
|
||||
onAudioOutputChangedListener.audioOutputChanged(selected);
|
||||
}
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
private final TextView textView;
|
||||
private final RadioButton radioButton;
|
||||
private final Consumer<Integer> onPressed;
|
||||
|
||||
|
||||
public ViewHolder(@NonNull View itemView, @NonNull Consumer<Integer> onPressed) {
|
||||
super(itemView);
|
||||
|
||||
this.textView = itemView.findViewById(R.id.text);
|
||||
this.radioButton = itemView.findViewById(R.id.radio);
|
||||
this.onPressed = onPressed;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void bind(@NonNull WebRtcAudioOutput audioOutput, @Nullable WebRtcAudioOutput selected) {
|
||||
textView.setText(audioOutput.getLabelRes());
|
||||
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(audioOutput.getIconRes(), 0, 0, 0);
|
||||
|
||||
radioButton.setOnCheckedChangeListener(null);
|
||||
radioButton.setChecked(audioOutput == selected);
|
||||
radioButton.setOnCheckedChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
int adapterPosition = getAdapterPosition();
|
||||
if (adapterPosition != RecyclerView.NO_POSITION) {
|
||||
onPressed.accept(adapterPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
public interface OnAudioOutputChangedListener {
|
||||
void audioOutputChanged(WebRtcAudioOutput audioOutput);
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import androidx.annotation.StringRes;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public enum WebRtcAudioOutput {
|
||||
HANDSET(R.string.WebRtcAudioOutputToggle__phone, R.drawable.ic_phone_right_black_28),
|
||||
SPEAKER(R.string.WebRtcAudioOutputToggle__speaker, R.drawable.ic_speaker_solid_black_28),
|
||||
HEADSET(R.string.WebRtcAudioOutputToggle__bluetooth, R.drawable.ic_speaker_bt_solid_black_28);
|
||||
HANDSET(R.string.WebRtcAudioOutputToggle__phone_earpiece, R.drawable.ic_handset_solid_24),
|
||||
SPEAKER(R.string.WebRtcAudioOutputToggle__speaker, R.drawable.ic_speaker_solid_24),
|
||||
HEADSET(R.string.WebRtcAudioOutputToggle__bluetooth, R.drawable.ic_speaker_bt_solid_24);
|
||||
|
||||
private final @StringRes int labelRes;
|
||||
private final @DrawableRes int iconRes;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
@@ -14,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,37 +23,48 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
|
||||
private static final String STATE_OUTPUT_INDEX = "audio.output.toggle.state.output.index";
|
||||
private static final String STATE_HEADSET_ENABLED = "audio.output.toggle.state.headset.enabled";
|
||||
private static final String STATE_HANDSET_ENABLED = "audio.output.toggle.state.handset.enabled";
|
||||
private static final String STATE_PARENT = "audio.output.toggle.state.parent";
|
||||
|
||||
private static final int[] OUTPUT_HANDSET = { R.attr.state_handset };
|
||||
private static final int[] OUTPUT_SPEAKER = { R.attr.state_speaker };
|
||||
private static final int[] OUTPUT_HEADSET = { R.attr.state_headset };
|
||||
private static final int[][] OUTPUT_ENUM = { OUTPUT_HANDSET, OUTPUT_SPEAKER, OUTPUT_HEADSET };
|
||||
private static final List<WebRtcAudioOutput> OUTPUT_MODES = Arrays.asList(WebRtcAudioOutput.HANDSET, WebRtcAudioOutput.SPEAKER, WebRtcAudioOutput.HEADSET);
|
||||
private static final WebRtcAudioOutput OUTPUT_FALLBACK = WebRtcAudioOutput.HANDSET;
|
||||
private static final int[] SPEAKER_OFF = { R.attr.state_speaker_off };
|
||||
private static final int[] SPEAKER_ON = { R.attr.state_speaker_on };
|
||||
private static final int[] OUTPUT_HANDSET = { R.attr.state_handset_selected };
|
||||
private static final int[] OUTPUT_SPEAKER = { R.attr.state_speaker_selected };
|
||||
private static final int[] OUTPUT_HEADSET = { R.attr.state_headset_selected };
|
||||
private static final int[][] OUTPUT_ENUM = { SPEAKER_OFF, SPEAKER_ON, OUTPUT_HANDSET, OUTPUT_SPEAKER, OUTPUT_HEADSET };
|
||||
private static final List<WebRtcAudioOutput> OUTPUT_MODES = Arrays.asList(WebRtcAudioOutput.HANDSET, WebRtcAudioOutput.SPEAKER, WebRtcAudioOutput.HANDSET, WebRtcAudioOutput.SPEAKER, WebRtcAudioOutput.HEADSET);
|
||||
|
||||
private boolean isHeadsetAvailable;
|
||||
private boolean isHandsetAvailable;
|
||||
private int outputIndex;
|
||||
private OnAudioOutputChangedListener audioOutputChangedListener;
|
||||
private AlertDialog picker;
|
||||
private DialogInterface picker;
|
||||
|
||||
public WebRtcAudioOutputToggleButton(Context context) {
|
||||
public WebRtcAudioOutputToggleButton(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public WebRtcAudioOutputToggleButton(Context context, AttributeSet attrs) {
|
||||
public WebRtcAudioOutputToggleButton(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public WebRtcAudioOutputToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public WebRtcAudioOutputToggleButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
super.setOnClickListener((v) -> {
|
||||
if (isHeadsetAvailable) showPicker();
|
||||
else setAudioOutput(OUTPUT_MODES.get((outputIndex + 1) % OUTPUT_ENUM.length));
|
||||
List<WebRtcAudioOutput> availableModes = buildOutputModeList(isHeadsetAvailable, isHandsetAvailable);
|
||||
|
||||
if (availableModes.size() > 2 || !isHandsetAvailable) showPicker(availableModes);
|
||||
else setAudioOutput(OUTPUT_MODES.get((outputIndex + 1) % OUTPUT_MODES.size()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
hidePicker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] extra = OUTPUT_ENUM[outputIndex];
|
||||
@@ -65,14 +78,14 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
throw new UnsupportedOperationException("This View does not support custom click listeners.");
|
||||
}
|
||||
|
||||
public void setIsHeadsetAvailable(boolean isHeadsetAvailable) {
|
||||
public void setControlAvailability(boolean isHandsetAvailable, boolean isHeadsetAvailable) {
|
||||
this.isHandsetAvailable = isHandsetAvailable;
|
||||
this.isHeadsetAvailable = isHeadsetAvailable;
|
||||
setAudioOutput(OUTPUT_MODES.get(outputIndex));
|
||||
}
|
||||
|
||||
public void setAudioOutput(@NonNull WebRtcAudioOutput audioOutput) {
|
||||
int oldIndex = outputIndex;
|
||||
outputIndex = resolveAudioOutputIndex(OUTPUT_MODES.indexOf(audioOutput), isHeadsetAvailable);
|
||||
outputIndex = resolveAudioOutputIndex(OUTPUT_MODES.lastIndexOf(audioOutput));
|
||||
|
||||
if (oldIndex != outputIndex) {
|
||||
refreshDrawableState();
|
||||
@@ -84,23 +97,26 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
this.audioOutputChangedListener = listener;
|
||||
}
|
||||
|
||||
private void showPicker() {
|
||||
RecyclerView rv = new RecyclerView(getContext());
|
||||
private void showPicker(@NonNull List<WebRtcAudioOutput> availableModes) {
|
||||
RecyclerView rv = new RecyclerView(getContext());
|
||||
AudioOutputAdapter adapter = new AudioOutputAdapter(audioOutput -> {
|
||||
setAudioOutput(audioOutput);
|
||||
hidePicker();
|
||||
},
|
||||
availableModes);
|
||||
|
||||
adapter.setSelectedOutput(OUTPUT_MODES.get(outputIndex));
|
||||
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
|
||||
rv.setAdapter(new AudioOutputAdapter(this::setAudioOutputViaDialog, OUTPUT_MODES));
|
||||
rv.setAdapter(adapter);
|
||||
|
||||
picker = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.WebRtcAudioOutputToggle__audio_output)
|
||||
.setView(rv)
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void hidePicker() {
|
||||
if (picker != null) {
|
||||
picker.dismiss();
|
||||
picker = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
Parcelable parentState = super.onSaveInstanceState();
|
||||
@@ -109,6 +125,7 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
bundle.putParcelable(STATE_PARENT, parentState);
|
||||
bundle.putInt(STATE_OUTPUT_INDEX, outputIndex);
|
||||
bundle.putBoolean(STATE_HEADSET_ENABLED, isHeadsetAvailable);
|
||||
bundle.putBoolean(STATE_HANDSET_ENABLED, isHandsetAvailable);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@@ -118,8 +135,10 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
Bundle savedState = (Bundle) state;
|
||||
|
||||
isHeadsetAvailable = savedState.getBoolean(STATE_HEADSET_ENABLED);
|
||||
isHandsetAvailable = savedState.getBoolean(STATE_HANDSET_ENABLED);
|
||||
|
||||
setAudioOutput(OUTPUT_MODES.get(
|
||||
resolveAudioOutputIndex(savedState.getInt(STATE_OUTPUT_INDEX), isHeadsetAvailable))
|
||||
resolveAudioOutputIndex(savedState.getInt(STATE_OUTPUT_INDEX)))
|
||||
);
|
||||
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_PARENT));
|
||||
@@ -128,36 +147,60 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView {
|
||||
}
|
||||
}
|
||||
|
||||
private void hidePicker() {
|
||||
if (picker != null) {
|
||||
picker.dismiss();
|
||||
picker = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListener() {
|
||||
if (audioOutputChangedListener == null) return;
|
||||
|
||||
audioOutputChangedListener.audioOutputChanged(OUTPUT_MODES.get(outputIndex));
|
||||
}
|
||||
|
||||
private void setAudioOutputViaDialog(@NonNull WebRtcAudioOutput audioOutput) {
|
||||
setAudioOutput(audioOutput);
|
||||
hidePicker();
|
||||
}
|
||||
private static List<WebRtcAudioOutput> buildOutputModeList(boolean isHeadsetAvailable, boolean isHandsetAvailable) {
|
||||
List<WebRtcAudioOutput> modes = new ArrayList(3);
|
||||
|
||||
private static int resolveAudioOutputIndex(int desiredAudioOutputIndex, boolean isHeadsetAvailable) {
|
||||
modes.add(WebRtcAudioOutput.SPEAKER);
|
||||
|
||||
if (isHeadsetAvailable) {
|
||||
modes.add(WebRtcAudioOutput.HEADSET);
|
||||
}
|
||||
|
||||
if (isHandsetAvailable) {
|
||||
modes.add(WebRtcAudioOutput.HANDSET);
|
||||
}
|
||||
|
||||
return modes;
|
||||
};
|
||||
|
||||
private int resolveAudioOutputIndex(int desiredAudioOutputIndex) {
|
||||
if (isIllegalAudioOutputIndex(desiredAudioOutputIndex)) {
|
||||
throw new IllegalArgumentException("Unsupported index: " + desiredAudioOutputIndex);
|
||||
}
|
||||
if (isUnsupportedAudioOutput(desiredAudioOutputIndex, isHeadsetAvailable)) {
|
||||
return OUTPUT_MODES.indexOf(OUTPUT_FALLBACK);
|
||||
if (isUnsupportedAudioOutput(desiredAudioOutputIndex, isHeadsetAvailable, isHandsetAvailable)) {
|
||||
if (!isHandsetAvailable) {
|
||||
return OUTPUT_MODES.lastIndexOf(WebRtcAudioOutput.SPEAKER);
|
||||
} else {
|
||||
return OUTPUT_MODES.indexOf(WebRtcAudioOutput.HANDSET);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHeadsetAvailable) {
|
||||
return desiredAudioOutputIndex % 2;
|
||||
}
|
||||
|
||||
return desiredAudioOutputIndex;
|
||||
}
|
||||
|
||||
private static boolean isIllegalAudioOutputIndex(int desiredFlashIndex) {
|
||||
return desiredFlashIndex < 0 || desiredFlashIndex > OUTPUT_ENUM.length;
|
||||
private static boolean isIllegalAudioOutputIndex(int desiredAudioOutputIndex) {
|
||||
return desiredAudioOutputIndex < 0 || desiredAudioOutputIndex > OUTPUT_MODES.size();
|
||||
}
|
||||
|
||||
private static boolean isUnsupportedAudioOutput(int desiredAudioOutputIndex, boolean isHeadsetAvailable) {
|
||||
return OUTPUT_MODES.get(desiredAudioOutputIndex) == WebRtcAudioOutput.HEADSET && !isHeadsetAvailable;
|
||||
}
|
||||
|
||||
public interface OnAudioOutputChangedListener {
|
||||
void audioOutputChanged(WebRtcAudioOutput audioOutput);
|
||||
private static boolean isUnsupportedAudioOutput(int desiredAudioOutputIndex, boolean isHeadsetAvailable, boolean isHandsetAvailable) {
|
||||
return (OUTPUT_MODES.get(desiredAudioOutputIndex) == WebRtcAudioOutput.HEADSET && !isHeadsetAvailable) ||
|
||||
(OUTPUT_MODES.get(desiredAudioOutputIndex) == WebRtcAudioOutput.HANDSET && !isHandsetAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||
@@ -29,7 +28,6 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -40,19 +38,20 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.webrtc.RendererCommon;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class WebRtcCallView extends FrameLayout {
|
||||
|
||||
private static final String TAG = Log.tag(WebRtcCallView.class);
|
||||
private static final long TRANSITION_DURATION_MILLIS = 250;
|
||||
private static final long TRANSITION_DURATION_MILLIS = 250;
|
||||
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
|
||||
private static final int LARGE_ONGOING_CALL_BUTTON_MARGIN_DP = 16;
|
||||
private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
|
||||
|
||||
public static final int FADE_OUT_DELAY = 5000;
|
||||
|
||||
private TextureViewRenderer localRenderer;
|
||||
private WebRtcAudioOutputToggleButton speakerToggle;
|
||||
private WebRtcAudioOutputToggleButton audioToggle;
|
||||
private AccessibleToggleButton videoToggle;
|
||||
private AccessibleToggleButton micToggle;
|
||||
private ViewGroup largeLocalRenderContainer;
|
||||
@@ -68,18 +67,21 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private RecipientId recipientId;
|
||||
private CameraState.Direction cameraDirection;
|
||||
private ImageView answer;
|
||||
private View cameraDirectionToggle;
|
||||
private ImageView cameraDirectionToggle;
|
||||
private PictureInPictureGestureHelper pictureInPictureGestureHelper;
|
||||
private ImageView hangup;
|
||||
private View answerWithAudio;
|
||||
private View answerWithAudioLabel;
|
||||
private View ongoingFooterGradient;
|
||||
|
||||
private Set<View> ongoingAudioCallViews;
|
||||
private Set<View> ongoingVideoCallViews;
|
||||
private Set<View> incomingAudioCallViews;
|
||||
private Set<View> incomingVideoCallViews;
|
||||
|
||||
private Set<View> currentVisibleViewSet = Collections.emptySet();
|
||||
private final Set<View> incomingCallViews = new HashSet<>();
|
||||
private final Set<View> topViews = new HashSet<>();
|
||||
private final Set<View> visibleViewSet = new HashSet<>();
|
||||
private final Set<View> adjustableMarginsSet = new HashSet<>();
|
||||
|
||||
private WebRtcControls controls = WebRtcControls.NONE;
|
||||
private final Runnable fadeOutRunnable = () -> { if (isAttachedToWindow() && shouldFadeControls(controls)) fadeOutControls(); };
|
||||
private final Runnable fadeOutRunnable = () -> {
|
||||
if (isAttachedToWindow() && controls.isFadeOutEnabled()) fadeOutControls(); };
|
||||
|
||||
public WebRtcCallView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
@@ -96,9 +98,9 @@ public class WebRtcCallView extends FrameLayout {
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
speakerToggle = findViewById(R.id.call_screen_speaker_toggle);
|
||||
audioToggle = findViewById(R.id.call_screen_speaker_toggle);
|
||||
videoToggle = findViewById(R.id.call_screen_video_toggle);
|
||||
micToggle = findViewById(R.id.call_screen_mic_toggle);
|
||||
micToggle = findViewById(R.id.call_screen_audio_mic_toggle);
|
||||
localRenderPipFrame = findViewById(R.id.call_screen_pip);
|
||||
largeLocalRenderContainer = findViewById(R.id.call_screen_large_local_renderer_holder);
|
||||
smallLocalRenderContainer = findViewById(R.id.call_screen_small_local_renderer_holder);
|
||||
@@ -110,31 +112,34 @@ public class WebRtcCallView extends FrameLayout {
|
||||
avatarCard = findViewById(R.id.call_screen_recipient_avatar_call_card);
|
||||
answer = findViewById(R.id.call_screen_answer_call);
|
||||
cameraDirectionToggle = findViewById(R.id.call_screen_camera_direction_toggle);
|
||||
hangup = findViewById(R.id.call_screen_end_call);
|
||||
answerWithAudio = findViewById(R.id.call_screen_answer_with_audio);
|
||||
answerWithAudioLabel = findViewById(R.id.call_screen_answer_with_audio_label);
|
||||
ongoingFooterGradient = findViewById(R.id.call_screen_ongoing_footer_gradient);
|
||||
|
||||
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
||||
View hangup = findViewById(R.id.call_screen_end_call);
|
||||
View downCaret = findViewById(R.id.call_screen_down_arrow);
|
||||
View decline = findViewById(R.id.call_screen_decline_call);
|
||||
View answerWithAudio = findViewById(R.id.call_screen_answer_with_audio);
|
||||
View answerWithAudioLabel = findViewById(R.id.call_screen_answer_with_audio_label);
|
||||
View answerLabel = findViewById(R.id.call_screen_answer_call_label);
|
||||
View declineLabel = findViewById(R.id.call_screen_decline_call_label);
|
||||
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
||||
View downCaret = findViewById(R.id.call_screen_down_arrow);
|
||||
View decline = findViewById(R.id.call_screen_decline_call);
|
||||
View answerLabel = findViewById(R.id.call_screen_answer_call_label);
|
||||
View declineLabel = findViewById(R.id.call_screen_decline_call_label);
|
||||
View incomingFooterGradient = findViewById(R.id.call_screen_incoming_footer_gradient);
|
||||
|
||||
Set<View> topAreaViews = Sets.newHashSet(status, topGradient, recipientName);
|
||||
topViews.add(status);
|
||||
topViews.add(topGradient);
|
||||
topViews.add(recipientName);
|
||||
|
||||
incomingAudioCallViews = Sets.newHashSet(decline, declineLabel, answer, answerLabel);
|
||||
incomingAudioCallViews.addAll(topAreaViews);
|
||||
incomingCallViews.add(answer);
|
||||
incomingCallViews.add(answerLabel);
|
||||
incomingCallViews.add(decline);
|
||||
incomingCallViews.add(declineLabel);
|
||||
incomingCallViews.add(incomingFooterGradient);
|
||||
|
||||
incomingVideoCallViews = Sets.newHashSet(decline, declineLabel, answer, answerLabel, answerWithAudio, answerWithAudioLabel);
|
||||
incomingVideoCallViews.addAll(topAreaViews);
|
||||
adjustableMarginsSet.add(micToggle);
|
||||
adjustableMarginsSet.add(cameraDirectionToggle);
|
||||
adjustableMarginsSet.add(videoToggle);
|
||||
adjustableMarginsSet.add(audioToggle);
|
||||
|
||||
ongoingAudioCallViews = Sets.newHashSet(micToggle, speakerToggle, videoToggle, hangup);
|
||||
ongoingAudioCallViews.addAll(topAreaViews);
|
||||
|
||||
ongoingVideoCallViews = Sets.newHashSet();
|
||||
ongoingVideoCallViews.addAll(ongoingAudioCallViews);
|
||||
|
||||
speakerToggle.setOnAudioOutputChangedListener(outputMode -> {
|
||||
audioToggle.setOnAudioOutputChangedListener(outputMode -> {
|
||||
runIfNonNull(controlsListener, listener -> listener.onAudioOutputChanged(outputMode));
|
||||
});
|
||||
|
||||
@@ -172,7 +177,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (shouldFadeControls(controls)) {
|
||||
if (controls.isFadeOutEnabled()) {
|
||||
scheduleFadeOut();
|
||||
}
|
||||
}
|
||||
@@ -183,10 +188,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
cancelFadeOut();
|
||||
}
|
||||
|
||||
public void showCameraToggleButton(boolean shouldShowCameraToggleButton) {
|
||||
cameraDirectionToggle.setVisibility(shouldShowCameraToggleButton ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void setControlsListener(@Nullable ControlsListener controlsListener) {
|
||||
this.controlsListener = controlsListener;
|
||||
}
|
||||
@@ -195,12 +196,8 @@ public class WebRtcCallView extends FrameLayout {
|
||||
micToggle.setChecked(isMicEnabled, false);
|
||||
}
|
||||
|
||||
public void setBluetoothEnabled(boolean isBluetoothEnabled) {
|
||||
speakerToggle.setIsHeadsetAvailable(isBluetoothEnabled);
|
||||
}
|
||||
|
||||
public void setAudioOutput(WebRtcAudioOutput output) {
|
||||
speakerToggle.setAudioOutput(output);
|
||||
audioToggle.setAudioOutput(output);
|
||||
}
|
||||
|
||||
public void setRemoteVideoEnabled(boolean isRemoteVideoEnabled) {
|
||||
@@ -239,14 +236,12 @@ public class WebRtcCallView extends FrameLayout {
|
||||
case GONE:
|
||||
localRenderPipFrame.setVisibility(View.GONE);
|
||||
largeLocalRenderContainer.setVisibility(View.GONE);
|
||||
cameraDirectionToggle.animate().setDuration(0).alpha(0f);
|
||||
setRenderer(largeLocalRenderContainer, null);
|
||||
setRenderer(smallLocalRenderContainer, null);
|
||||
break;
|
||||
case LARGE:
|
||||
localRenderPipFrame.setVisibility(View.GONE);
|
||||
largeLocalRenderContainer.setVisibility(View.VISIBLE);
|
||||
cameraDirectionToggle.animate().setDuration(0).alpha(0f);
|
||||
if (largeLocalRenderContainer.getChildCount() == 0) {
|
||||
setRenderer(largeLocalRenderContainer, localRenderer);
|
||||
}
|
||||
@@ -254,9 +249,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
case SMALL:
|
||||
localRenderPipFrame.setVisibility(View.VISIBLE);
|
||||
largeLocalRenderContainer.setVisibility(View.GONE);
|
||||
cameraDirectionToggle.animate()
|
||||
.setDuration(450)
|
||||
.alpha(1f);
|
||||
|
||||
if (smallLocalRenderContainer.getChildCount() == 0) {
|
||||
setRenderer(smallLocalRenderContainer, localRenderer);
|
||||
@@ -315,54 +307,71 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void setWebRtcControls(WebRtcControls webRtcControls) {
|
||||
Set<View> lastVisibleSet = currentVisibleViewSet;
|
||||
Set<View> lastVisibleSet = new HashSet<>(visibleViewSet);
|
||||
|
||||
Log.d(TAG, "Setting Controls: " + controls.name() + " -> " + webRtcControls.name());
|
||||
visibleViewSet.clear();
|
||||
visibleViewSet.addAll(topViews);
|
||||
|
||||
switch (webRtcControls) {
|
||||
case NONE:
|
||||
currentVisibleViewSet = Collections.emptySet();
|
||||
cancelFadeOut();
|
||||
break;
|
||||
case INCOMING_VIDEO:
|
||||
currentVisibleViewSet = incomingVideoCallViews;
|
||||
status.setText(R.string.WebRtcCallView__signal_video_call);
|
||||
answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer_with_video));
|
||||
cancelFadeOut();
|
||||
break;
|
||||
case INCOMING_AUDIO:
|
||||
currentVisibleViewSet = incomingAudioCallViews;
|
||||
status.setText(R.string.WebRtcCallView__signal_voice_call);
|
||||
answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer));
|
||||
cancelFadeOut();
|
||||
break;
|
||||
case ONGOING_LOCAL_AUDIO_REMOTE_AUDIO:
|
||||
currentVisibleViewSet = ongoingAudioCallViews;
|
||||
cancelFadeOut();
|
||||
break;
|
||||
case ONGOING_LOCAL_AUDIO_REMOTE_VIDEO:
|
||||
currentVisibleViewSet = ongoingAudioCallViews;
|
||||
if (!shouldFadeControls(controls)) {
|
||||
scheduleFadeOut();
|
||||
}
|
||||
break;
|
||||
case ONGOING_LOCAL_VIDEO_REMOTE_AUDIO:
|
||||
currentVisibleViewSet = ongoingVideoCallViews;
|
||||
cancelFadeOut();
|
||||
break;
|
||||
case ONGOING_LOCAL_VIDEO_REMOTE_VIDEO:
|
||||
currentVisibleViewSet = ongoingVideoCallViews;
|
||||
if (!shouldFadeControls(controls)) {
|
||||
scheduleFadeOut();
|
||||
}
|
||||
break;
|
||||
if (webRtcControls.displayIncomingCallButtons()) {
|
||||
visibleViewSet.addAll(incomingCallViews);
|
||||
|
||||
status.setText(R.string.WebRtcCallView__signal_voice_call);
|
||||
answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer));
|
||||
}
|
||||
|
||||
if (webRtcControls.displayAnswerWithAudio()) {
|
||||
visibleViewSet.add(answerWithAudio);
|
||||
visibleViewSet.add(answerWithAudioLabel);
|
||||
|
||||
status.setText(R.string.WebRtcCallView__signal_video_call);
|
||||
answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer_with_video));
|
||||
}
|
||||
|
||||
if (webRtcControls.displayAudioToggle()) {
|
||||
visibleViewSet.add(audioToggle);
|
||||
|
||||
audioToggle.setControlAvailability(webRtcControls.enableHandsetInAudioToggle(),
|
||||
webRtcControls.enableHeadsetInAudioToggle());
|
||||
|
||||
audioToggle.setAudioOutput(webRtcControls.getAudioOutput());
|
||||
}
|
||||
|
||||
if (webRtcControls.displayCameraToggle()) {
|
||||
visibleViewSet.add(cameraDirectionToggle);
|
||||
}
|
||||
|
||||
if (webRtcControls.displayEndCall()) {
|
||||
visibleViewSet.add(hangup);
|
||||
visibleViewSet.add(ongoingFooterGradient);
|
||||
}
|
||||
|
||||
if (webRtcControls.displayMuteAudio()) {
|
||||
visibleViewSet.add(micToggle);
|
||||
}
|
||||
|
||||
if (webRtcControls.displayVideoToggle()) {
|
||||
visibleViewSet.add(videoToggle);
|
||||
}
|
||||
|
||||
if (webRtcControls.displaySmallOngoingCallButtons()) {
|
||||
updateButtonStateForSmallButtons();
|
||||
} else if (webRtcControls.displayLargeOngoingCallButtons()) {
|
||||
updateButtonStateForLargeButtons();
|
||||
}
|
||||
|
||||
if (webRtcControls.isFadeOutEnabled()) {
|
||||
if (!controls.isFadeOutEnabled()) {
|
||||
scheduleFadeOut();
|
||||
}
|
||||
} else {
|
||||
cancelFadeOut();
|
||||
}
|
||||
|
||||
controls = webRtcControls;
|
||||
|
||||
if (!currentVisibleViewSet.equals(lastVisibleSet) || !shouldFadeControls(controls)) {
|
||||
fadeInNewUiState(lastVisibleSet);
|
||||
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), speakerToggle.getTop()));
|
||||
if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) {
|
||||
fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons());
|
||||
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), videoToggle.getTop()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +380,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
|
||||
private void toggleControls() {
|
||||
if (shouldFadeControls(controls) && status.getVisibility() == VISIBLE) {
|
||||
if (controls.isFadeOutEnabled() && status.getVisibility() == VISIBLE) {
|
||||
fadeOutControls();
|
||||
} else {
|
||||
fadeInControls();
|
||||
@@ -386,7 +395,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
|
||||
private void fadeInControls() {
|
||||
fadeControls(ConstraintSet.VISIBLE);
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), speakerToggle.getTop());
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), videoToggle.getTop());
|
||||
|
||||
scheduleFadeOut();
|
||||
}
|
||||
@@ -399,14 +408,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.clone(parent);
|
||||
|
||||
for (View view : currentVisibleViewSet) {
|
||||
for (View view : visibleViewSet) {
|
||||
constraintSet.setVisibility(view.getId(), visibility);
|
||||
}
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
}
|
||||
|
||||
private void fadeInNewUiState(@NonNull Set<View> previouslyVisibleViewSet) {
|
||||
private void fadeInNewUiState(@NonNull Set<View> previouslyVisibleViewSet, boolean useSmallMargins) {
|
||||
Transition transition = new AutoTransition().setDuration(TRANSITION_DURATION_MILLIS);
|
||||
|
||||
TransitionManager.beginDelayedTransition(parent, transition);
|
||||
@@ -414,12 +423,19 @@ public class WebRtcCallView extends FrameLayout {
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.clone(parent);
|
||||
|
||||
for (View view : SetUtil.difference(previouslyVisibleViewSet, currentVisibleViewSet)) {
|
||||
for (View view : SetUtil.difference(previouslyVisibleViewSet, visibleViewSet)) {
|
||||
constraintSet.setVisibility(view.getId(), ConstraintSet.GONE);
|
||||
}
|
||||
|
||||
for (View view : currentVisibleViewSet) {
|
||||
for (View view : visibleViewSet) {
|
||||
constraintSet.setVisibility(view.getId(), ConstraintSet.VISIBLE);
|
||||
|
||||
if (adjustableMarginsSet.contains(view)) {
|
||||
constraintSet.setMargin(view.getId(),
|
||||
ConstraintSet.END,
|
||||
ViewUtil.dpToPx(useSmallMargins ? SMALL_ONGOING_CALL_BUTTON_MARGIN_DP
|
||||
: LARGE_ONGOING_CALL_BUTTON_MARGIN_DP));
|
||||
}
|
||||
}
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
@@ -477,8 +493,20 @@ public class WebRtcCallView extends FrameLayout {
|
||||
this.avatarCard.setBackgroundColor(recipient.getColor().toActionBarColor(getContext()));
|
||||
}
|
||||
|
||||
private static boolean shouldFadeControls(@NonNull WebRtcControls controls) {
|
||||
return controls == WebRtcControls.ONGOING_LOCAL_AUDIO_REMOTE_VIDEO || controls == WebRtcControls.ONGOING_LOCAL_VIDEO_REMOTE_VIDEO;
|
||||
private void updateButtonStateForLargeButtons() {
|
||||
cameraDirectionToggle.setImageResource(R.drawable.webrtc_call_screen_camera_toggle);
|
||||
hangup.setImageResource(R.drawable.webrtc_call_screen_hangup);
|
||||
micToggle.setBackgroundResource(R.drawable.webrtc_call_screen_mic_toggle);
|
||||
videoToggle.setBackgroundResource(R.drawable.webrtc_call_screen_video_toggle);
|
||||
audioToggle.setImageResource(R.drawable.webrtc_call_screen_speaker_toggle);
|
||||
}
|
||||
|
||||
private void updateButtonStateForSmallButtons() {
|
||||
cameraDirectionToggle.setImageResource(R.drawable.webrtc_call_screen_camera_toggle_small);
|
||||
hangup.setImageResource(R.drawable.webrtc_call_screen_hangup_small);
|
||||
micToggle.setBackgroundResource(R.drawable.webrtc_call_screen_mic_toggle_small);
|
||||
videoToggle.setBackgroundResource(R.drawable.webrtc_call_screen_video_toggle_small);
|
||||
audioToggle.setImageResource(R.drawable.webrtc_call_screen_speaker_toggle_small);
|
||||
}
|
||||
|
||||
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
|
||||
|
||||
@@ -20,14 +20,11 @@ import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
public class WebRtcCallViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<Boolean> remoteVideoEnabled = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<WebRtcAudioOutput> audioOutput = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> bluetoothEnabled = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<WebRtcLocalRenderState> localRenderState = new MutableLiveData<>(WebRtcLocalRenderState.GONE);
|
||||
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<Boolean> localVideoEnabled = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<CameraState.Direction> cameraDirection = new MutableLiveData<>(CameraState.Direction.FRONT);
|
||||
private final MutableLiveData<Boolean> hasMultipleCameras = new MutableLiveData<>(false);
|
||||
private final LiveData<Boolean> shouldDisplayLocal = LiveDataUtil.combineLatest(isInPipMode, localVideoEnabled, (a, b) -> !a && b);
|
||||
private final LiveData<WebRtcLocalRenderState> realLocalRenderState = LiveDataUtil.combineLatest(shouldDisplayLocal, localRenderState, this::getRealLocalRenderState);
|
||||
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
|
||||
@@ -46,22 +43,10 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository();
|
||||
|
||||
public WebRtcCallViewModel() {
|
||||
audioOutput.setValue(repository.getAudioOutput());
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getRemoteVideoEnabled() {
|
||||
return Transformations.distinctUntilChanged(remoteVideoEnabled);
|
||||
}
|
||||
|
||||
public LiveData<WebRtcAudioOutput> getAudioOutput() {
|
||||
return Transformations.distinctUntilChanged(audioOutput);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getBluetoothEnabled() {
|
||||
return Transformations.distinctUntilChanged(bluetoothEnabled);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getMicrophoneEnabled() {
|
||||
return Transformations.distinctUntilChanged(microphoneEnabled);
|
||||
}
|
||||
@@ -98,10 +83,6 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
return Transformations.map(ellapsed, timeInCall -> callConnectedTime == -1 ? -1 : timeInCall);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isMoreThanOneCameraAvailable() {
|
||||
return hasMultipleCameras;
|
||||
}
|
||||
|
||||
public boolean isAnswerWithVideoAvailable() {
|
||||
return answerWithVideoAvailable;
|
||||
}
|
||||
@@ -118,21 +99,21 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
@MainThread
|
||||
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel) {
|
||||
remoteVideoEnabled.setValue(webRtcViewModel.isRemoteVideoEnabled());
|
||||
bluetoothEnabled.setValue(webRtcViewModel.isBluetoothAvailable());
|
||||
audioOutput.setValue(repository.getAudioOutput());
|
||||
microphoneEnabled.setValue(webRtcViewModel.isMicrophoneEnabled());
|
||||
|
||||
if (isValidCameraDirectionForUi(webRtcViewModel.getLocalCameraState().getActiveDirection())) {
|
||||
cameraDirection.setValue(webRtcViewModel.getLocalCameraState().getActiveDirection());
|
||||
}
|
||||
|
||||
hasMultipleCameras.setValue(webRtcViewModel.getLocalCameraState().getCameraCount() > 0);
|
||||
localVideoEnabled.setValue(webRtcViewModel.getLocalCameraState().isEnabled());
|
||||
updateLocalRenderState(webRtcViewModel.getState());
|
||||
updateWebRtcControls(webRtcViewModel.getState(),
|
||||
webRtcViewModel.getLocalCameraState().isEnabled(),
|
||||
webRtcViewModel.isRemoteVideoEnabled(),
|
||||
webRtcViewModel.isRemoteVideoOffer());
|
||||
webRtcViewModel.isRemoteVideoOffer(),
|
||||
webRtcViewModel.getLocalCameraState().getCameraCount() > 1,
|
||||
webRtcViewModel.isBluetoothAvailable(),
|
||||
repository.getAudioOutput());
|
||||
|
||||
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
|
||||
callConnectedTime = webRtcViewModel.getCallConnectedTime();
|
||||
@@ -167,23 +148,32 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWebRtcControls(WebRtcViewModel.State state, boolean isLocalVideoEnabled, boolean isRemoteVideoEnabled, boolean isRemoteVideoOffer) {
|
||||
private void updateWebRtcControls(WebRtcViewModel.State state,
|
||||
boolean isLocalVideoEnabled,
|
||||
boolean isRemoteVideoEnabled,
|
||||
boolean isRemoteVideoOffer,
|
||||
boolean isMoreThanOneCameraAvailable,
|
||||
boolean isBluetoothAvailable,
|
||||
WebRtcAudioOutput audioOutput)
|
||||
{
|
||||
|
||||
final WebRtcControls.CallState callState;
|
||||
|
||||
switch (state) {
|
||||
case CALL_INCOMING:
|
||||
webRtcControls.setValue(isRemoteVideoOffer ? WebRtcControls.INCOMING_VIDEO : WebRtcControls.INCOMING_AUDIO);
|
||||
callState = WebRtcControls.CallState.INCOMING;
|
||||
answerWithVideoAvailable = isRemoteVideoOffer;
|
||||
break;
|
||||
default:
|
||||
if (isLocalVideoEnabled && isRemoteVideoEnabled) {
|
||||
webRtcControls.setValue(WebRtcControls.ONGOING_LOCAL_VIDEO_REMOTE_VIDEO);
|
||||
} else if (isLocalVideoEnabled) {
|
||||
webRtcControls.setValue(WebRtcControls.ONGOING_LOCAL_VIDEO_REMOTE_AUDIO);
|
||||
} else if (isRemoteVideoEnabled) {
|
||||
webRtcControls.setValue(WebRtcControls.ONGOING_LOCAL_AUDIO_REMOTE_VIDEO);
|
||||
} else {
|
||||
webRtcControls.setValue(WebRtcControls.ONGOING_LOCAL_AUDIO_REMOTE_AUDIO);
|
||||
}
|
||||
callState = WebRtcControls.CallState.ONGOING;
|
||||
}
|
||||
|
||||
webRtcControls.setValue(new WebRtcControls(isLocalVideoEnabled,
|
||||
isRemoteVideoEnabled || isRemoteVideoOffer,
|
||||
isMoreThanOneCameraAvailable,
|
||||
isBluetoothAvailable,
|
||||
callState,
|
||||
audioOutput));
|
||||
}
|
||||
|
||||
private @NonNull WebRtcLocalRenderState getRealLocalRenderState(boolean shouldDisplayLocalVideo, @NonNull WebRtcLocalRenderState state) {
|
||||
|
||||
@@ -1,11 +1,100 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
public enum WebRtcControls {
|
||||
NONE,
|
||||
ONGOING_LOCAL_AUDIO_REMOTE_AUDIO,
|
||||
ONGOING_LOCAL_AUDIO_REMOTE_VIDEO,
|
||||
ONGOING_LOCAL_VIDEO_REMOTE_AUDIO,
|
||||
ONGOING_LOCAL_VIDEO_REMOTE_VIDEO,
|
||||
INCOMING_AUDIO,
|
||||
INCOMING_VIDEO
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class WebRtcControls {
|
||||
|
||||
public static final WebRtcControls NONE = new WebRtcControls();
|
||||
|
||||
private final boolean isRemoteVideoEnabled;
|
||||
private final boolean isLocalVideoEnabled;
|
||||
private final boolean isMoreThanOneCameraAvailable;
|
||||
private final boolean isBluetoothAvailable;
|
||||
private final CallState callState;
|
||||
private final WebRtcAudioOutput audioOutput;
|
||||
|
||||
private WebRtcControls() {
|
||||
this(false, false, false, false, CallState.NONE, WebRtcAudioOutput.HANDSET);
|
||||
}
|
||||
|
||||
WebRtcControls(boolean isLocalVideoEnabled,
|
||||
boolean isRemoteVideoEnabled,
|
||||
boolean isMoreThanOneCameraAvailable,
|
||||
boolean isBluetoothAvailable,
|
||||
@NonNull CallState callState,
|
||||
@NonNull WebRtcAudioOutput audioOutput)
|
||||
{
|
||||
this.isLocalVideoEnabled = isLocalVideoEnabled;
|
||||
this.isRemoteVideoEnabled = isRemoteVideoEnabled;
|
||||
this.isBluetoothAvailable = isBluetoothAvailable;
|
||||
this.isMoreThanOneCameraAvailable = isMoreThanOneCameraAvailable;
|
||||
this.callState = callState;
|
||||
this.audioOutput = audioOutput;
|
||||
}
|
||||
|
||||
boolean displayEndCall() {
|
||||
return isOngoing();
|
||||
}
|
||||
|
||||
boolean displayMuteAudio() {
|
||||
return isOngoing();
|
||||
}
|
||||
|
||||
boolean displayVideoToggle() {
|
||||
return isOngoing();
|
||||
}
|
||||
|
||||
boolean displayAudioToggle() {
|
||||
return isOngoing() && (!isLocalVideoEnabled || isBluetoothAvailable);
|
||||
}
|
||||
|
||||
boolean displayCameraToggle() {
|
||||
return isOngoing() && isLocalVideoEnabled && isMoreThanOneCameraAvailable;
|
||||
}
|
||||
|
||||
boolean displayAnswerWithAudio() {
|
||||
return isIncoming() && isRemoteVideoEnabled;
|
||||
}
|
||||
|
||||
boolean displayIncomingCallButtons() {
|
||||
return isIncoming();
|
||||
}
|
||||
|
||||
boolean enableHandsetInAudioToggle() {
|
||||
return !isLocalVideoEnabled;
|
||||
}
|
||||
|
||||
boolean enableHeadsetInAudioToggle() {
|
||||
return isBluetoothAvailable;
|
||||
}
|
||||
|
||||
boolean isFadeOutEnabled() {
|
||||
return isOngoing() && isRemoteVideoEnabled;
|
||||
}
|
||||
|
||||
boolean displaySmallOngoingCallButtons() {
|
||||
return isOngoing() && displayAudioToggle() && displayCameraToggle();
|
||||
}
|
||||
|
||||
boolean displayLargeOngoingCallButtons() {
|
||||
return isOngoing() && !(displayAudioToggle() && displayCameraToggle());
|
||||
}
|
||||
|
||||
WebRtcAudioOutput getAudioOutput() {
|
||||
return audioOutput;
|
||||
}
|
||||
|
||||
private boolean isOngoing() {
|
||||
return callState == CallState.ONGOING;
|
||||
}
|
||||
|
||||
private boolean isIncoming() {
|
||||
return callState == CallState.INCOMING;
|
||||
}
|
||||
|
||||
public enum CallState {
|
||||
NONE,
|
||||
INCOMING,
|
||||
ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,13 +482,12 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
boolean isSpeaker = intent.getBooleanExtra(EXTRA_SPEAKER, false);
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.stopBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.setSpeakerphoneOn(true);
|
||||
audioManager.setSpeakerphoneOn(isSpeaker);
|
||||
|
||||
if (isSpeaker && audioManager.isBluetoothScoOn()) {
|
||||
audioManager.stopBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
}
|
||||
|
||||
if (!localCameraState.isEnabled()) {
|
||||
lockManager.updatePhoneState(getInCallPhoneState());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user