Implement layout changes to new call screen UX.

This commit is contained in:
Alex Hart 2020-05-21 16:57:21 -03:00
parent 5cb1201903
commit 124c3e25e9
37 changed files with 783 additions and 293 deletions

View file

@ -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);

View file

@ -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);
}
}
}
}

View file

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.components.webrtc;
public interface OnAudioOutputChangedListener {
void audioOutputChanged(WebRtcAudioOutput audioOutput);
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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) {

View file

@ -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
}
}

View file

@ -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());
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/core_grey_75"
android:pathData="M19.78,4.22 L18.72,5.28a9.52,9.52 0,0 1,0 13.44l1.06,1.06A11,11 0,0 0,19.78 4.22ZM19,12A7,7 0,0 0,17 7.05L15.89,8.11a5.5,5.5 0,0 1,0 7.78L17,17A7,7 0,0 0,19 12ZM14,3.14V20.86a0.5,0.5 0,0 1,-0.84 0.37L8,16.5H4a2,2 0,0 1,-2 -2v-5a2,2 0,0 1,2 -2H8l5.16,-4.73A0.5,0.5 0,0 1,14 3.14Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/core_grey_75"
android:pathData="M22,12L20.5,12A8.51,8.51 0,0 0,12 3.48L12,2A10,10 0,0 1,22 12ZM12,6L12,7.5A4.49,4.49 0,0 1,16.49 12L18,12A6,6 0,0 0,12 6ZM21.85,17.31a1.46,1.46 0,0 1,0.09 1,8.54 8.54,0 0,1 -0.44,1.13 4.22,4.22 0,0 1,-1.81 2,4.3 4.3,0 0,1 -2.63,0.52v0A17,17 0,0 1,2 6.89L2,6.89a4.22,4.22 0,0 1,0.53 -2.6,4.28 4.28,0 0,1 2,-1.79 9.62,9.62 0,0 1,1.1 -0.42,1.37 1.37,0 0,1 1,0.09 1.41,1.41 0,0 1,0.7 0.74,25.83 25.83,0 0,0 2.53,4.5 1.45,1.45 0,0 1,-0.4 2,9.09 9.09,0 0,1 -1.35,0.78 4.42,4.42 0,0 1,-1.24 0.36,13.14 13.14,0 0,0 6.56,6.56 5,5 0,0 1,0.35 -1.25,10.23 10.23,0 0,1 0.87,-1.46 1.38,1.38 0,0 1,1.92 -0.29,26.14 26.14,0 0,0 4.51,2.5A1.46,1.46 0,0 1,21.85 17.31Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_white"
android:pathData="M6.31,3.75a2.19,2.19 0,0 1,2.81 0.69l2.4,3.43a2.18,2.18 0,0 1,-0.28 2.85c-0.41,0.39 -0.69,0.64 -1,0.95a1.12,1.12 0,0 0,-0.21 1.5,23.7 23.7,0 0,0 2.24,2.6A23.7,23.7 0,0 0,14.83 18a1.12,1.12 0,0 0,1.5 -0.21c0.31,-0.35 0.56,-0.63 1,-1a2.18,2.18 0,0 1,2.85 -0.28l3.43,2.4a2.19,2.19 0,0 1,0.69 2.81,4.83 4.83,0 0,1 -1.67,2c-0.83,0.59 -2.38,1.36 -5.41,0.29A22.9,22.9 0,0 1,4 10.83c-1.07,-3 -0.3,-4.58 0.29,-5.41A4.83,4.83 0,0 1,6.31 3.75ZM21.76,6.22a10.56,10.56 0,0 0,-8.91 -3l0.2,1.49A9.15,9.15 0,0 1,23.3 15l1.49,0.2A10.58,10.58 0,0 0,21.76 6.22ZM19.14,8.83a6.27,6.27 0,0 0,-5.4 -1.76L14,8.55a4.81,4.81 0,0 1,4.11 1.34,4.69 4.69,0 0,1 1.35,4l1.49,0.2A6.21,6.21 0,0 0,19.14 8.83Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_black"
android:pathData="M25.56,3.56l-22,22L2.5,24.5l4.67,-4.67A9.22,9.22 0,0 1,5.5 14.5L7,14.5a7.74,7.74 0,0 0,1.25 4.25l1.37,-1.37A4.9,4.9 0,0 1,9 15L9,7A5,5 0,0 1,19 7L19,8l5.5,-5.5ZM19,15L19,12.24l-7.22,7.22A5,5 0,0 0,19 15ZM14,22a6.62,6.62 0,0 1,-3.65 -1.11L9.28,22a8.09,8.09 0,0 0,4 1.5L13.28,28h1.5L14.78,23.46a8.82,8.82 0,0 0,7.75 -9L21,14.46A7.27,7.27 0,0 1,14 22Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_white"
android:pathData="M25.56,3.56l-22,22L2.5,24.5l4.67,-4.67A9.22,9.22 0,0 1,5.5 14.5L7,14.5a7.74,7.74 0,0 0,1.25 4.25l1.37,-1.37A4.9,4.9 0,0 1,9 15L9,7A5,5 0,0 1,19 7L19,8l5.5,-5.5ZM19,15L19,12.24l-7.22,7.22A5,5 0,0 0,19 15ZM14,22a6.62,6.62 0,0 1,-3.65 -1.11L9.28,22a8.09,8.09 0,0 0,4 1.5L13.28,28h1.5L14.78,23.46a8.82,8.82 0,0 0,7.75 -9L21,14.46A7.27,7.27 0,0 1,14 22Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_white"
android:pathData="M4.31,5.42C3.72,6.25 3,7.8 4,10.83A22.9,22.9 0,0 0,17.17 24c3,1.07 4.58,0.3 5.41,-0.29a4.83,4.83 0,0 0,1.67 -2,2.19 2.19,0 0,0 -0.69,-2.81l-3.43,-2.4a2.18,2.18 0,0 0,-2.85 0.28c-0.39,0.41 -0.64,0.69 -1,1a1.12,1.12 0,0 1,-1.5 0.21,23.7 23.7,0 0,1 -2.6,-2.24A23.7,23.7 0,0 1,10 13.17a1.12,1.12 0,0 1,0.21 -1.5c0.35,-0.31 0.63,-0.56 1,-0.95a2.18,2.18 0,0 0,0.28 -2.85L9.12,4.44a2.19,2.19 0,0 0,-2.81 -0.69A4.83,4.83 0,0 0,4.31 5.42Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/core_grey_75"
android:pathData="M14,3.14L14,20.86a0.5,0.5 0,0 1,-0.84 0.37L8,16.5L4,16.5a2,2 0,0 1,-2 -2v-5a2,2 0,0 1,2 -2L8,7.5l5.16,-4.73A0.5,0.5 0,0 1,14 3.14ZM19.82,12l2.53,-2.53a0.75,0.75 0,0 0,0 -1.06L19.29,5.35a0.76,0.76 0,0 0,-0.82 -0.16,0.74 0.74,0 0,0 -0.46,0.69v4.57l-1.78,-2L15.17,9.47 17.7,12l-2.53,2.53 1.06,1.06h0l1.78,-2v4.57h0a0.74,0.74 0,0 0,0.46 0.69,0.72 0.72,0 0,0 0.29,0.06 0.79,0.79 0,0 0,0.53 -0.22l3.06,-3.06a0.75,0.75 0,0 0,0 -1.06ZM19.51,7.69 L20.75,8.94 19.51,10.19ZM19.51,16.31v-2.5l1.24,1.25Z"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_white"
android:fillColor="@color/core_grey_75"
android:pathData="M16,4.79L16,23.21a1,1 0,0 1,-1.68 0.73L9,19L5,19a3,3 0,0 1,-3 -3L2,12A3,3 0,0 1,5 9L9,9l5.32,-4.94A1,1 0,0 1,16 4.79ZM22.53,22.53 L26.53,18.53a0.75,0.75 0,0 0,0 -1.06L23.06,14l3.47,-3.47a0.75,0.75 0,0 0,0 -1.06l-4,-4A0.75,0.75 0,0 0,21.25 6v6.19L18.53,9.47l-1.06,1.06L20.94,14l-3.47,3.47 1.06,1.06 2.72,-2.72L21.25,22a0.74,0.74 0,0 0,0.46 0.69,0.74 0.74,0 0,0 0.82,-0.16ZM24.94,18l-2.19,2.19L22.75,15.81ZM24.94,10 L22.75,12.19L22.75,7.81Z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_black"
android:pathData="M16,4.79L16,23.21a1,1 0,0 1,-1.68 0.73L9,19L5,19a3,3 0,0 1,-3 -3L2,12A3,3 0,0 1,5 9L9,9l5.32,-4.94A1,1 0,0 1,16 4.79ZM22.53,22.53 L26.53,18.53a0.75,0.75 0,0 0,0 -1.06L23.06,14l3.47,-3.47a0.75,0.75 0,0 0,0 -1.06l-4,-4A0.75,0.75 0,0 0,21.25 6v6.19L18.53,9.47l-1.06,1.06L20.94,14l-3.47,3.47 1.06,1.06 2.72,-2.72L21.25,22a0.74,0.74 0,0 0,0.46 0.69,0.74 0.74,0 0,0 0.82,-0.16ZM24.94,18l-2.19,2.19L22.75,15.81ZM24.94,10 L22.75,12.19L22.75,7.81Z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_black"
android:pathData="M16,4.79L16,23.21a1,1 0,0 1,-1.68 0.73L9,19L5,19a3,3 0,0 1,-3 -3L2,12A3,3 0,0 1,5 9L9,9l5.32,-4.94A1,1 0,0 1,16 4.79ZM27,14A12.91,12.91 0,0 0,21.92 3.69L21,4.88a11.5,11.5 0,0 1,0 18.27l0.91,1.19A12.92,12.92 0,0 0,27 14ZM23,14a9.06,9.06 0,0 0,-3.7 -7.28l-0.89,1.22a7.5,7.5 0,0 1,0.12 12l0.91,1.19A8.94,8.94 0,0 0,23 14Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_white"
android:pathData="M23.53,10.63l-4,4 1.06,1.06 1.48,-1.48L22.78,13v1a8.78,8.78 0,0 1,-15 6.21L6.73,21.27A10.28,10.28 0,0 0,24.28 14V13L25,14.21l1.48,1.48 1.06,-1.06Z"/>
<path
android:fillColor="@color/core_white"
android:pathData="M7.41,12.31 L5.93,13.79 5.22,15L5.22,14a8.78,8.78 0,0 1,15 -6.21l1.06,-1.06A10.28,10.28 0,0 0,3.72 14v1L3,13.79 1.53,12.31 0.47,13.37l4,4 4,-4ZM4.47,16.31Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/core_grey_75"
android:pathData="M22,12l3.29,-3.29a1,1 0,0 1,1.71 0.7v9.18a1,1 0,0 1,-1.71 0.7L22,16ZM20.5,19L20.5,9a3,3 0,0 0,-3 -3L6,6A3,3 0,0 0,3 9L3,19a3,3 0,0 0,3 3L17.5,22A3,3 0,0 0,20.5 19Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/webrtc_call_screen_circle_grey_selector" />
<item
android:bottom="14dp"
android:drawable="@drawable/ic_switch_camera_28"
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_grey_selector" />
<item
android:bottom="14dp"
android:drawable="@drawable/ic_switch_camera_28"
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_red" />
<item
android:bottom="14dp"
android:drawable="@drawable/ic_phone_down_28"
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>

View file

@ -3,7 +3,7 @@
<item android:state_checked="true">
<layer-list>
<item android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_mic_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
<item android:bottom="14dp" android:drawable="@drawable/ic_mic_off_solid_28_white" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_mic_off_solid_28_white" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item>
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_mic_off_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
</selector>

View file

@ -1,21 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item app:state_handset="true">
<item app:state_speaker_off="true">
<layer-list>
<item android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_white_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item app:state_speaker="true">
<item app:state_speaker_on="true">
<layer-list>
<item android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item app:state_headset="true">
<item app:state_handset_selected="true">
<layer-list>
<item android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_bt_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
<item android:bottom="14dp" android:drawable="@drawable/ic_handset_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_white" android:left="43dp" android:right="5dp" android:top="27dp" />
</layer-list>
</item>
<item app:state_headset_selected="true">
<layer-list>
<item android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_bt_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_grey" android:left="43dp" android:right="5dp" android:top="27dp" />
</layer-list>
</item>
<item app:state_speaker_selected="true">
<layer-list>
<item android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_grey" android:left="43dp" android:right="5dp" android:top="27dp" />
</layer-list>
</item>
</selector>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item app:state_speaker_off="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_white_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item app:state_speaker_on="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item app:state_handset_selected="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_handset_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_white" android:left="43dp" android:right="7dp" android:top="27dp" />
</layer-list>
</item>
<item app:state_headset_selected="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_bt_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_grey" android:left="43dp" android:right="7dp" android:top="27dp" />
</layer-list>
</item>
<item app:state_speaker_selected="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_speaker_solid_28" android:left="11dp" android:right="17dp" android:top="14dp" />
<item android:bottom="25dp" android:drawable="@drawable/webrtc_triangle_grey" android:left="43dp" android:right="7dp" android:top="27dp" />
</layer-list>
</item>
</selector>

View file

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_video_solid_grey_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item>
<layer-list>
<item android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_video_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item>
<layer-list>
<item android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_video_off_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
</selector>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/circle_tintable" />
<item android:bottom="14dp" android:drawable="@drawable/ic_video_solid_grey_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
<item>
<layer-list>
<item
android:bottom="4dp"
android:top="4dp"
android:left="4dp"
android:right="4dp"
android:drawable="@drawable/webrtc_call_screen_circle_grey" />
<item android:bottom="14dp" android:drawable="@drawable/ic_video_solid_28" android:left="14dp" android:right="14dp" android:top="14dp" />
</layer-list>
</item>
</selector>

View file

@ -0,0 +1,7 @@
<vector android:height="4dp" android:viewportHeight="203.66562"
android:viewportWidth="203.66562" android:width="8dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="@color/core_grey_75" android:pathData="M50.919,100.832L0.999,1.002L100.839,1.002L200.669,1.002L150.759,100.832L100.839,200.662L50.919,100.832Z"/>
<path android:fillAlpha="0" android:fillColor="#FF000000"
android:pathData="M50.919,100.832L0.999,1.002L100.839,1.002L200.669,1.002L150.759,100.832L100.839,200.662L50.919,100.832Z"
android:strokeAlpha="1" android:strokeWidth="0"/>
</vector>

View file

@ -0,0 +1,7 @@
<vector android:height="4dp" android:viewportHeight="203.66562"
android:viewportWidth="203.66562" android:width="8dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="@color/core_white" android:pathData="M50.919,100.832L0.999,1.002L100.839,1.002L200.669,1.002L150.759,100.832L100.839,200.662L50.919,100.832Z"/>
<path android:fillAlpha="0" android:fillColor="#FF000000"
android:pathData="M50.919,100.832L0.999,1.002L100.839,1.002L200.669,1.002L150.759,100.832L100.839,200.662L50.919,100.832Z"
android:strokeAlpha="1" android:strokeWidth="0"/>
</vector>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeight"
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textColor="@color/black"
android:textSize="16sp"
tools:drawableStart="@drawable/ic_photo_solid_24"
tools:text="@string/WebRtcAudioOutputToggle__phone" />

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeight">
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textColor="@color/black"
android:textSize="16sp"
tools:drawableStart="@drawable/ic_photo_solid_24"
tools:text="@string/WebRtcAudioOutputToggle__phone" />
<RadioButton
android:id="@+id/radio"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="16dp" />
</LinearLayout>

View file

@ -54,6 +54,26 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/call_screen_ongoing_footer_gradient"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="@drawable/webrtc_call_screen_header_gradient"
android:rotation="180"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="gone" />
<View
android:id="@+id/call_screen_incoming_footer_gradient"
android:layout_width="match_parent"
android:layout_height="240dp"
android:background="@drawable/webrtc_call_screen_header_gradient"
android:rotation="180"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible" />
<org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout
android:id="@+id/call_screen_pip_area"
android:layout_width="0dp"
@ -83,15 +103,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/call_screen_camera_direction_toggle"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="bottom|center_horizontal"
android:paddingStart="9dp"
android:paddingEnd="9dp"
android:paddingBottom="10dp"
android:src="@drawable/ic_switch_camera_32" />
</androidx.cardview.widget.CardView>
</org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout>
@ -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" />
<ImageView
android:id="@+id/call_screen_camera_direction_toggle"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="34dp"
android:clickable="false"
android:scaleType="fitXY"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_screen_video_toggle"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/webrtc_call_screen_speaker_toggle"
app:layout_constraintStart_toEndOf="@id/call_screen_speaker_toggle"
app:srcCompat="@drawable/webrtc_call_screen_camera_toggle"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.AccessibleToggleButton
@ -159,13 +187,14 @@
android:stateListAnimator="@null"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_screen_mic_toggle"
app:layout_constraintEnd_toStartOf="@id/call_screen_audio_mic_toggle"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/call_screen_speaker_toggle"
app:layout_constraintStart_toEndOf="@id/call_screen_camera_direction_toggle"
tools:checked="true"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.AccessibleToggleButton
android:id="@+id/call_screen_mic_toggle"
android:id="@+id/call_screen_audio_mic_toggle"
style="@style/WebRtcCallV2CompoundButton"
android:layout_marginEnd="16dp"
android:layout_marginBottom="34dp"
@ -184,12 +213,13 @@
android:layout_height="56dp"
android:layout_marginBottom="34dp"
android:clickable="false"
android:src="@drawable/webrtc_call_screen_hangup"
android:scaleType="fitXY"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/call_screen_mic_toggle"
app:layout_constraintStart_toEndOf="@id/call_screen_audio_mic_toggle"
app:srcCompat="@drawable/webrtc_call_screen_hangup"
tools:visibility="visible" />
<ImageView
@ -198,12 +228,12 @@
android:layout_height="56dp"
android:layout_marginStart="66dp"
android:layout_marginBottom="65dp"
android:src="@drawable/webrtc_call_screen_hangup"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_screen_answer_call"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/webrtc_call_screen_hangup"
tools:visibility="visible" />
<TextView
@ -225,12 +255,12 @@
android:layout_height="56dp"
android:layout_marginEnd="66dp"
android:layout_marginBottom="65dp"
android:src="@drawable/webrtc_call_screen_answer"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toEndOf="@id/call_screen_decline_call"
app:srcCompat="@drawable/webrtc_call_screen_answer"
tools:visibility="visible" />
<TextView
@ -251,11 +281,11 @@
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginBottom="5dp"
android:src="@drawable/webrtc_call_screen_answer_without_video"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/call_screen_answer_with_audio_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/webrtc_call_screen_answer_without_video"
tools:visibility="visible" />
<TextView

View file

@ -516,8 +516,10 @@
</declare-styleable>
<declare-styleable name="WebRtcAudioOutputToggleButtonState">
<attr name="state_headset" format="boolean" />
<attr name="state_speaker" format="boolean" />
<attr name="state_handset" format="boolean" />
<attr name="state_speaker_on" format="boolean" />
<attr name="state_speaker_off" format="boolean" />
<attr name="state_headset_selected" format="boolean" />
<attr name="state_speaker_selected" format="boolean" />
<attr name="state_handset_selected" format="boolean" />
</declare-styleable>
</resources>

View file

@ -1294,7 +1294,8 @@
<string name="WebRtcCallScreen__answer_without_video">Answer without video</string>
<!-- WebRtcAudioOutputToggle -->
<string name="WebRtcAudioOutputToggle__phone">Phone</string>
<string name="WebRtcAudioOutputToggle__audio_output">Audio output</string>
<string name="WebRtcAudioOutputToggle__phone_earpiece">Phone earpiece</string>
<string name="WebRtcAudioOutputToggle__speaker">Speaker</string>
<string name="WebRtcAudioOutputToggle__bluetooth">Bluetooth</string>