From 124c3e25e97817334f291f004ea4fd289ac7bde9 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 21 May 2020 16:57:21 -0300 Subject: [PATCH] Implement layout changes to new call screen UX. --- .../securesms/WebRtcCallActivity.java | 23 +- .../components/webrtc/AudioOutputAdapter.java | 90 +++++-- .../webrtc/OnAudioOutputChangedListener.java | 5 + .../components/webrtc/WebRtcAudioOutput.java | 6 +- .../webrtc/WebRtcAudioOutputToggleButton.java | 125 ++++++---- .../components/webrtc/WebRtcCallView.java | 230 ++++++++++-------- .../webrtc/WebRtcCallViewModel.java | 58 ++--- .../components/webrtc/WebRtcControls.java | 105 +++++++- .../securesms/service/WebRtcCallService.java | 9 +- .../res/drawable-hdpi/ic_speaker_solid_24.xml | 9 + .../main/res/drawable/ic_handset_solid_24.xml | 9 + .../main/res/drawable/ic_handset_solid_28.xml | 9 + .../drawable/ic_mic_off_solid_28_black.xml | 9 + .../drawable/ic_mic_off_solid_28_white.xml | 9 + .../res/drawable/ic_phone_right_white_28.xml | 9 + .../res/drawable/ic_speaker_bt_solid_24.xml | 9 + .../res/drawable/ic_speaker_bt_solid_28.xml | 2 +- .../drawable/ic_speaker_bt_solid_black_28.xml | 9 - .../drawable/ic_speaker_solid_black_28.xml | 9 - .../main/res/drawable/ic_switch_camera_28.xml | 12 + .../res/drawable/ic_video_solid_grey_28.xml | 9 + .../webrtc_call_screen_camera_toggle.xml | 10 + ...webrtc_call_screen_camera_toggle_small.xml | 15 ++ .../webrtc_call_screen_hangup_small.xml | 15 ++ .../webrtc_call_screen_mic_toggle.xml | 2 +- .../webrtc_call_screen_mic_toggle_small.xml | 25 ++ .../webrtc_call_screen_speaker_toggle.xml | 23 +- ...ebrtc_call_screen_speaker_toggle_small.xml | 61 +++++ .../webrtc_call_screen_video_toggle.xml | 12 +- .../webrtc_call_screen_video_toggle_small.xml | 25 ++ .../res/drawable/webrtc_triangle_grey.xml | 7 + .../res/drawable/webrtc_triangle_white.xml | 7 + .../res/layout/audio_output_adapter_item.xml | 13 - .../audio_output_adapter_radio_item.xml | 27 ++ app/src/main/res/layout/webrtc_call_view.xml | 68 ++++-- app/src/main/res/values/attrs.xml | 8 +- app/src/main/res/values/strings.xml | 3 +- 37 files changed, 783 insertions(+), 293 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OnAudioOutputChangedListener.java create mode 100644 app/src/main/res/drawable-hdpi/ic_speaker_solid_24.xml create mode 100644 app/src/main/res/drawable/ic_handset_solid_24.xml create mode 100644 app/src/main/res/drawable/ic_handset_solid_28.xml create mode 100644 app/src/main/res/drawable/ic_mic_off_solid_28_black.xml create mode 100644 app/src/main/res/drawable/ic_mic_off_solid_28_white.xml create mode 100644 app/src/main/res/drawable/ic_phone_right_white_28.xml create mode 100644 app/src/main/res/drawable/ic_speaker_bt_solid_24.xml delete mode 100644 app/src/main/res/drawable/ic_speaker_bt_solid_black_28.xml delete mode 100644 app/src/main/res/drawable/ic_speaker_solid_black_28.xml create mode 100644 app/src/main/res/drawable/ic_switch_camera_28.xml create mode 100644 app/src/main/res/drawable/ic_video_solid_grey_28.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_camera_toggle.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_camera_toggle_small.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_hangup_small.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_mic_toggle_small.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_speaker_toggle_small.xml create mode 100644 app/src/main/res/drawable/webrtc_call_screen_video_toggle_small.xml create mode 100644 app/src/main/res/drawable/webrtc_triangle_grey.xml create mode 100644 app/src/main/res/drawable/webrtc_triangle_white.xml delete mode 100644 app/src/main/res/layout/audio_output_adapter_item.xml create mode 100644 app/src/main/res/layout/audio_output_adapter_radio_item.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index 28e6e0092a..1d79be3afd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioOutputAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioOutputAdapter.java index 012bc49295..dc9f6b2dcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioOutputAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioOutputAdapter.java @@ -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 { +final class AudioOutputAdapter extends RecyclerView.Adapter { - private final Consumer consumer; - private final List audioOutputs; + private final OnAudioOutputChangedListener onAudioOutputChangedListener; + private final List audioOutputs; - AudioOutputAdapter(@NonNull Consumer consumer, @NonNull List audioOutputs) { - this.audioOutputs = audioOutputs; - this.consumer = consumer; + private WebRtcAudioOutput selected; + + AudioOutputAdapter(@NonNull OnAudioOutputChangedListener onAudioOutputChangedListener, + @NonNull List 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 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 onPressed; + + + public ViewHolder(@NonNull View itemView, @NonNull Consumer 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); + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OnAudioOutputChangedListener.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OnAudioOutputChangedListener.java new file mode 100644 index 0000000000..b70a7792dd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OnAudioOutputChangedListener.java @@ -0,0 +1,5 @@ +package org.thoughtcrime.securesms.components.webrtc; + +public interface OnAudioOutputChangedListener { + void audioOutputChanged(WebRtcAudioOutput audioOutput); +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutput.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutput.java index ef1bc2c315..d262f83462 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutput.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutput.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java index 584abb24e6..f4c1fd6f21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java @@ -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 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 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 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 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 buildOutputModeList(boolean isHeadsetAvailable, boolean isHandsetAvailable) { + List 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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java index 25ce12f000..7c28b0c8c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java @@ -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 ongoingAudioCallViews; - private Set ongoingVideoCallViews; - private Set incomingAudioCallViews; - private Set incomingVideoCallViews; - - private Set currentVisibleViewSet = Collections.emptySet(); + private final Set incomingCallViews = new HashSet<>(); + private final Set topViews = new HashSet<>(); + private final Set visibleViewSet = new HashSet<>(); + private final Set 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 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 lastVisibleSet = currentVisibleViewSet; + Set 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 previouslyVisibleViewSet) { + private void fadeInNewUiState(@NonNull Set 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java index 46ee602f4d..b8109723a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java @@ -20,14 +20,11 @@ import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; public class WebRtcCallViewModel extends ViewModel { private final MutableLiveData remoteVideoEnabled = new MutableLiveData<>(false); - private final MutableLiveData audioOutput = new MutableLiveData<>(); - private final MutableLiveData bluetoothEnabled = new MutableLiveData<>(false); private final MutableLiveData microphoneEnabled = new MutableLiveData<>(true); private final MutableLiveData localRenderState = new MutableLiveData<>(WebRtcLocalRenderState.GONE); private final MutableLiveData isInPipMode = new MutableLiveData<>(false); private final MutableLiveData localVideoEnabled = new MutableLiveData<>(false); private final MutableLiveData cameraDirection = new MutableLiveData<>(CameraState.Direction.FRONT); - private final MutableLiveData hasMultipleCameras = new MutableLiveData<>(false); private final LiveData shouldDisplayLocal = LiveDataUtil.combineLatest(isInPipMode, localVideoEnabled, (a, b) -> !a && b); private final LiveData realLocalRenderState = LiveDataUtil.combineLatest(shouldDisplayLocal, localRenderState, this::getRealLocalRenderState); private final MutableLiveData 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 getRemoteVideoEnabled() { return Transformations.distinctUntilChanged(remoteVideoEnabled); } - public LiveData getAudioOutput() { - return Transformations.distinctUntilChanged(audioOutput); - } - - public LiveData getBluetoothEnabled() { - return Transformations.distinctUntilChanged(bluetoothEnabled); - } - public LiveData 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 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java index 7072be5a46..362535bde9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java @@ -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 + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index 2c1b4d2f65..ad52e361b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -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()); } diff --git a/app/src/main/res/drawable-hdpi/ic_speaker_solid_24.xml b/app/src/main/res/drawable-hdpi/ic_speaker_solid_24.xml new file mode 100644 index 0000000000..fe4c351f0a --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_speaker_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_handset_solid_24.xml b/app/src/main/res/drawable/ic_handset_solid_24.xml new file mode 100644 index 0000000000..3877cb3acb --- /dev/null +++ b/app/src/main/res/drawable/ic_handset_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_handset_solid_28.xml b/app/src/main/res/drawable/ic_handset_solid_28.xml new file mode 100644 index 0000000000..c759884f65 --- /dev/null +++ b/app/src/main/res/drawable/ic_handset_solid_28.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_mic_off_solid_28_black.xml b/app/src/main/res/drawable/ic_mic_off_solid_28_black.xml new file mode 100644 index 0000000000..7ab9b7cadc --- /dev/null +++ b/app/src/main/res/drawable/ic_mic_off_solid_28_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_mic_off_solid_28_white.xml b/app/src/main/res/drawable/ic_mic_off_solid_28_white.xml new file mode 100644 index 0000000000..4f0a05968d --- /dev/null +++ b/app/src/main/res/drawable/ic_mic_off_solid_28_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone_right_white_28.xml b/app/src/main/res/drawable/ic_phone_right_white_28.xml new file mode 100644 index 0000000000..e4b1e300d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone_right_white_28.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_speaker_bt_solid_24.xml b/app/src/main/res/drawable/ic_speaker_bt_solid_24.xml new file mode 100644 index 0000000000..6d00b83bfc --- /dev/null +++ b/app/src/main/res/drawable/ic_speaker_bt_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_speaker_bt_solid_28.xml b/app/src/main/res/drawable/ic_speaker_bt_solid_28.xml index 1c14c424ec..f59ef1ebc8 100644 --- a/app/src/main/res/drawable/ic_speaker_bt_solid_28.xml +++ b/app/src/main/res/drawable/ic_speaker_bt_solid_28.xml @@ -4,6 +4,6 @@ android:viewportWidth="28" android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/ic_speaker_bt_solid_black_28.xml b/app/src/main/res/drawable/ic_speaker_bt_solid_black_28.xml deleted file mode 100644 index 0b42179ba3..0000000000 --- a/app/src/main/res/drawable/ic_speaker_bt_solid_black_28.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_speaker_solid_black_28.xml b/app/src/main/res/drawable/ic_speaker_solid_black_28.xml deleted file mode 100644 index 0d7525bcdd..0000000000 --- a/app/src/main/res/drawable/ic_speaker_solid_black_28.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_switch_camera_28.xml b/app/src/main/res/drawable/ic_switch_camera_28.xml new file mode 100644 index 0000000000..ae216a9ffb --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_camera_28.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_video_solid_grey_28.xml b/app/src/main/res/drawable/ic_video_solid_grey_28.xml new file mode 100644 index 0000000000..a78fb67677 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_solid_grey_28.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/webrtc_call_screen_camera_toggle.xml b/app/src/main/res/drawable/webrtc_call_screen_camera_toggle.xml new file mode 100644 index 0000000000..1b5dbfd042 --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_camera_toggle.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_camera_toggle_small.xml b/app/src/main/res/drawable/webrtc_call_screen_camera_toggle_small.xml new file mode 100644 index 0000000000..754cbcb9e8 --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_camera_toggle_small.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_hangup_small.xml b/app/src/main/res/drawable/webrtc_call_screen_hangup_small.xml new file mode 100644 index 0000000000..5e92c32408 --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_hangup_small.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_mic_toggle.xml b/app/src/main/res/drawable/webrtc_call_screen_mic_toggle.xml index 82f17a8c8b..2c5ead266e 100644 --- a/app/src/main/res/drawable/webrtc_call_screen_mic_toggle.xml +++ b/app/src/main/res/drawable/webrtc_call_screen_mic_toggle.xml @@ -3,7 +3,7 @@ - + diff --git a/app/src/main/res/drawable/webrtc_call_screen_mic_toggle_small.xml b/app/src/main/res/drawable/webrtc_call_screen_mic_toggle_small.xml new file mode 100644 index 0000000000..28767a553e --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_mic_toggle_small.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle.xml b/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle.xml index ae79a9d9b3..c3f05a2497 100644 --- a/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle.xml +++ b/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle.xml @@ -1,21 +1,36 @@ - + - + - + - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle_small.xml b/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle_small.xml new file mode 100644 index 0000000000..76f2b8f83c --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_speaker_toggle_small.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_video_toggle.xml b/app/src/main/res/drawable/webrtc_call_screen_video_toggle.xml index 19f37ceb86..8a61765031 100644 --- a/app/src/main/res/drawable/webrtc_call_screen_video_toggle.xml +++ b/app/src/main/res/drawable/webrtc_call_screen_video_toggle.xml @@ -1,15 +1,15 @@ + + + + + + - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_call_screen_video_toggle_small.xml b/app/src/main/res/drawable/webrtc_call_screen_video_toggle_small.xml new file mode 100644 index 0000000000..e65cbbbee2 --- /dev/null +++ b/app/src/main/res/drawable/webrtc_call_screen_video_toggle_small.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/webrtc_triangle_grey.xml b/app/src/main/res/drawable/webrtc_triangle_grey.xml new file mode 100644 index 0000000000..17e5c3a73f --- /dev/null +++ b/app/src/main/res/drawable/webrtc_triangle_grey.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/webrtc_triangle_white.xml b/app/src/main/res/drawable/webrtc_triangle_white.xml new file mode 100644 index 0000000000..0dd10ec370 --- /dev/null +++ b/app/src/main/res/drawable/webrtc_triangle_white.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/layout/audio_output_adapter_item.xml b/app/src/main/res/layout/audio_output_adapter_item.xml deleted file mode 100644 index 65a64acf3d..0000000000 --- a/app/src/main/res/layout/audio_output_adapter_item.xml +++ /dev/null @@ -1,13 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/audio_output_adapter_radio_item.xml b/app/src/main/res/layout/audio_output_adapter_radio_item.xml new file mode 100644 index 0000000000..c0969de7e4 --- /dev/null +++ b/app/src/main/res/layout/audio_output_adapter_radio_item.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/app/src/main/res/layout/webrtc_call_view.xml b/app/src/main/res/layout/webrtc_call_view.xml index 4e8a8b6694..69ac51eb10 100644 --- a/app/src/main/res/layout/webrtc_call_view.xml +++ b/app/src/main/res/layout/webrtc_call_view.xml @@ -54,6 +54,26 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + - @@ -142,12 +153,29 @@ android:layout_height="56dp" android:layout_marginEnd="16dp" android:layout_marginBottom="34dp" + android:scaleType="fitXY" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/call_screen_camera_direction_toggle" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:srcCompat="@drawable/webrtc_call_screen_speaker_toggle" + tools:visibility="visible" /> + + - - - + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2048ecf636..b55bde5b8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1294,7 +1294,8 @@ Answer without video - Phone + Audio output + Phone earpiece Speaker Bluetooth