From 8f183bdcdc7ba20e144af921c8928038b78c6cb9 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 18 May 2020 15:48:46 -0300 Subject: [PATCH] Consolidate Call UI visibility selection logic. --- .../securesms/WebRtcCallActivity.java | 1 + .../components/webrtc/WebRtcCallView.java | 183 ++++++++++-------- .../webrtc/WebRtcCallViewModel.java | 23 ++- .../components/webrtc/WebRtcControls.java | 7 +- app/src/main/res/layout/webrtc_call_view.xml | 69 +++---- 5 files changed, 157 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index 5705e754ec..28e6e0092a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -177,6 +177,7 @@ public class WebRtcCallActivity extends AppCompatActivity { private void processIntent(@NonNull Intent intent) { if (ANSWER_ACTION.equals(intent.getAction())) { + viewModel.setRecipient(EventBus.getDefault().getStickyEvent(WebRtcViewModel.class).getRecipient()); handleAnswerWithAudio(); } else if (DENY_ACTION.equals(intent.getAction())) { handleDenyCall(); 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 98804c190d..900764eee6 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 @@ -15,13 +15,13 @@ import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; -import androidx.constraintlayout.widget.Group; import androidx.core.util.Consumer; import androidx.transition.AutoTransition; 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,28 +29,29 @@ 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; import org.thoughtcrime.securesms.ringrtc.CameraState; import org.thoughtcrime.securesms.util.AvatarUtil; +import org.thoughtcrime.securesms.util.SetUtil; 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.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 FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider(); public static final int FADE_OUT_DELAY = 5000; private TextureViewRenderer localRenderer; - private Group ongoingCallButtons; - private Group incomingCallButtons; - private Group answerWithVoiceGroup; - private Group topViews; - private View topGradient; private WebRtcAudioOutputToggleButton speakerToggle; private AccessibleToggleButton videoToggle; private AccessibleToggleButton micToggle; @@ -66,12 +67,19 @@ public class WebRtcCallView extends FrameLayout { private ControlsListener controlsListener; private RecipientId recipientId; private CameraState.Direction cameraDirection; - private boolean shouldFadeControls; - private ImageView accept; + private ImageView answer; private View cameraDirectionToggle; private PictureInPictureGestureHelper pictureInPictureGestureHelper; - private final Runnable fadeOutRunnable = () -> { if (isAttachedToWindow()) fadeOutControls(); }; + private Set ongoingAudioCallViews; + private Set ongoingVideoCallViews; + private Set incomingAudioCallViews; + private Set incomingVideoCallViews; + + private Set currentVisibleViewSet = Collections.emptySet(); + + private WebRtcControls controls = WebRtcControls.NONE; + private final Runnable fadeOutRunnable = () -> { if (isAttachedToWindow() && shouldFadeControls(controls)) fadeOutControls(); }; public WebRtcCallView(@NonNull Context context) { this(context, null); @@ -88,11 +96,6 @@ public class WebRtcCallView extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); - ongoingCallButtons = findViewById(R.id.call_screen_in_call_buttons); - incomingCallButtons = findViewById(R.id.call_screen_incoming_call_buttons); - answerWithVoiceGroup = findViewById(R.id.call_screen_answer_with_audio_button); - topViews = findViewById(R.id.call_screen_top_views); - topGradient = findViewById(R.id.call_screen_header_gradient); speakerToggle = findViewById(R.id.call_screen_speaker_toggle); videoToggle = findViewById(R.id.call_screen_video_toggle); micToggle = findViewById(R.id.call_screen_mic_toggle); @@ -105,13 +108,31 @@ public class WebRtcCallView extends FrameLayout { parent = findViewById(R.id.call_screen); avatar = findViewById(R.id.call_screen_recipient_avatar); avatarCard = findViewById(R.id.call_screen_recipient_avatar_call_card); - accept = findViewById(R.id.call_screen_answer_call); + answer = findViewById(R.id.call_screen_answer_call); cameraDirectionToggle = findViewById(R.id.call_screen_camera_direction_toggle); - 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 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); + + Set topAreaViews = Sets.newHashSet(status, topGradient, recipientName); + + incomingAudioCallViews = Sets.newHashSet(decline, declineLabel, answer, answerLabel); + incomingAudioCallViews.addAll(topAreaViews); + + incomingVideoCallViews = Sets.newHashSet(decline, declineLabel, answer, answerLabel, answerWithAudio, answerWithAudioLabel); + incomingVideoCallViews.addAll(topAreaViews); + + ongoingAudioCallViews = Sets.newHashSet(micToggle, speakerToggle, videoToggle, hangup); + ongoingAudioCallViews.addAll(topAreaViews); + + ongoingVideoCallViews = Sets.newHashSet(); + ongoingVideoCallViews.addAll(ongoingAudioCallViews); speakerToggle.setOnAudioOutputChangedListener(outputMode -> { runIfNonNull(controlsListener, listener -> listener.onAudioOutputChanged(outputMode)); @@ -132,7 +153,7 @@ public class WebRtcCallView extends FrameLayout { downCaret.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onDownCaretPressed)); - accept.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallPressed)); + answer.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallPressed)); answerWithAudio.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallWithVoiceOnlyPressed)); setOnClickListener(v -> toggleControls()); @@ -151,7 +172,7 @@ public class WebRtcCallView extends FrameLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (shouldFadeControls) { + if (shouldFadeControls(controls)) { scheduleFadeOut(); } } @@ -183,22 +204,11 @@ public class WebRtcCallView extends FrameLayout { } public void setRemoteVideoEnabled(boolean isRemoteVideoEnabled) { - boolean wasRemoteVideoEnabled = remoteRenderContainer.getVisibility() == View.VISIBLE; - - shouldFadeControls = isRemoteVideoEnabled; - if (isRemoteVideoEnabled) { remoteRenderContainer.setVisibility(View.VISIBLE); } else { remoteRenderContainer.setVisibility(View.GONE); } - - if (isRemoteVideoEnabled && !wasRemoteVideoEnabled) { - fadeInControls(); - } else if (!isRemoteVideoEnabled && wasRemoteVideoEnabled) { - fadeInControls(); - cancelFadeOut(); - } } public void setLocalRenderer(@Nullable TextureViewRenderer surfaceViewRenderer) { @@ -305,48 +315,55 @@ public class WebRtcCallView extends FrameLayout { } public void setWebRtcControls(WebRtcControls webRtcControls) { - answerWithVoiceGroup.setVisibility(View.GONE); + Set lastVisibleSet = currentVisibleViewSet; + + Log.d(TAG, "Setting Controls: " + controls.name() + " -> " + webRtcControls.name()); switch (webRtcControls) { case NONE: - ongoingCallButtons.setVisibility(View.GONE); - incomingCallButtons.setVisibility(View.GONE); - setTopViewsVisibility(View.GONE); + currentVisibleViewSet = Collections.emptySet(); + cancelFadeOut(); break; case INCOMING_VIDEO: - answerWithVoiceGroup.setVisibility(View.VISIBLE); - setTopViewsVisibility(View.VISIBLE); - ongoingCallButtons.setVisibility(View.GONE); - incomingCallButtons.setVisibility(View.VISIBLE); + currentVisibleViewSet = incomingVideoCallViews; status.setText(R.string.WebRtcCallView__signal_video_call); - accept.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer_with_video)); + answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer_with_video)); + cancelFadeOut(); break; case INCOMING_AUDIO: - setTopViewsVisibility(View.VISIBLE); - ongoingCallButtons.setVisibility(View.GONE); - incomingCallButtons.setVisibility(View.VISIBLE); + currentVisibleViewSet = incomingAudioCallViews; status.setText(R.string.WebRtcCallView__signal_voice_call); - accept.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer)); + answer.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.webrtc_call_screen_answer)); + cancelFadeOut(); break; - case RINGING: - setTopViewsVisibility(View.VISIBLE); - incomingCallButtons.setVisibility(View.GONE); - ongoingCallButtons.setVisibility(View.VISIBLE); + 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; - case CONNECTED: - setTopViewsVisibility(View.VISIBLE); - incomingCallButtons.setVisibility(View.GONE); - ongoingCallButtons.setVisibility(View.VISIBLE); - - post(() -> { - pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), speakerToggle.getTop()); - }); } - } - private void setTopViewsVisibility(int visibility) { - topViews.setVisibility(visibility); - topGradient.setVisibility(visibility); + controls = webRtcControls; + + if (!currentVisibleViewSet.equals(lastVisibleSet)) { + fadeInNewUiState(lastVisibleSet); + post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(status.getBottom(), speakerToggle.getTop())); + } } public @NonNull View getVideoTooltipTarget() { @@ -354,12 +371,10 @@ public class WebRtcCallView extends FrameLayout { } private void toggleControls() { - if (shouldFadeControls) { - if (status.getVisibility() == VISIBLE) { - fadeOutControls(); - } else { - fadeInControls(); - } + if (shouldFadeControls(controls) && status.getVisibility() == VISIBLE) { + fadeOutControls(); + } else { + fadeInControls(); } } @@ -384,28 +399,40 @@ public class WebRtcCallView extends FrameLayout { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(parent); - constraintSet.setVisibility(R.id.call_screen_in_call_buttons, visibility); - constraintSet.setVisibility(R.id.call_screen_top_views, visibility); + for (View view : currentVisibleViewSet) { + constraintSet.setVisibility(view.getId(), visibility); + } constraintSet.applyTo(parent); + } - topGradient.animate() - .alpha(visibility == ConstraintSet.VISIBLE ? 1f : 0f) - .setDuration(TRANSITION_DURATION_MILLIS) - .start(); + private void fadeInNewUiState(@NonNull Set previouslyVisibleViewSet) { + Transition transition = new AutoTransition().setDuration(TRANSITION_DURATION_MILLIS); + + TransitionManager.beginDelayedTransition(parent, transition); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(parent); + + for (View view : SetUtil.difference(previouslyVisibleViewSet, currentVisibleViewSet)) { + constraintSet.setVisibility(view.getId(), ConstraintSet.GONE); + } + + for (View view : currentVisibleViewSet) { + constraintSet.setVisibility(view.getId(), ConstraintSet.VISIBLE); + } + + constraintSet.applyTo(parent); } private void scheduleFadeOut() { cancelFadeOut(); - shouldFadeControls = true; if (getHandler() == null) return; getHandler().postDelayed(fadeOutRunnable, FADE_OUT_DELAY); } private void cancelFadeOut() { - shouldFadeControls = false; - if (getHandler() == null) return; getHandler().removeCallbacks(fadeOutRunnable); } @@ -450,6 +477,10 @@ 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 static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider { @Override public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() { 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 66c41187be..46ee602f4d 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 @@ -129,7 +129,10 @@ public class WebRtcCallViewModel extends ViewModel { hasMultipleCameras.setValue(webRtcViewModel.getLocalCameraState().getCameraCount() > 0); localVideoEnabled.setValue(webRtcViewModel.getLocalCameraState().isEnabled()); updateLocalRenderState(webRtcViewModel.getState()); - updateWebRtcControls(webRtcViewModel.getState(), webRtcViewModel.isRemoteVideoOffer()); + updateWebRtcControls(webRtcViewModel.getState(), + webRtcViewModel.getLocalCameraState().isEnabled(), + webRtcViewModel.isRemoteVideoEnabled(), + webRtcViewModel.isRemoteVideoOffer()); if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) { callConnectedTime = webRtcViewModel.getCallConnectedTime(); @@ -164,20 +167,22 @@ public class WebRtcCallViewModel extends ViewModel { } } - private void updateWebRtcControls(WebRtcViewModel.State state, boolean isRemoteVideoOffer) { + private void updateWebRtcControls(WebRtcViewModel.State state, boolean isLocalVideoEnabled, boolean isRemoteVideoEnabled, boolean isRemoteVideoOffer) { switch (state) { case CALL_INCOMING: webRtcControls.setValue(isRemoteVideoOffer ? WebRtcControls.INCOMING_VIDEO : WebRtcControls.INCOMING_AUDIO); answerWithVideoAvailable = isRemoteVideoOffer; break; - case CALL_CONNECTED: - webRtcControls.setValue(WebRtcControls.CONNECTED); - break; - case CALL_OUTGOING: - webRtcControls.setValue(WebRtcControls.RINGING); - break; default: - webRtcControls.setValue(WebRtcControls.ONGOING); + 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); + } } } 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 7f290f3563..7072be5a46 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 @@ -2,9 +2,10 @@ package org.thoughtcrime.securesms.components.webrtc; public enum WebRtcControls { NONE, - ONGOING, - RINGING, - CONNECTED, + ONGOING_LOCAL_AUDIO_REMOTE_AUDIO, + ONGOING_LOCAL_AUDIO_REMOTE_VIDEO, + ONGOING_LOCAL_VIDEO_REMOTE_AUDIO, + ONGOING_LOCAL_VIDEO_REMOTE_VIDEO, INCOMING_AUDIO, INCOMING_VIDEO } diff --git a/app/src/main/res/layout/webrtc_call_view.xml b/app/src/main/res/layout/webrtc_call_view.xml index e781dc423e..4e8a8b6694 100644 --- a/app/src/main/res/layout/webrtc_call_view.xml +++ b/app/src/main/res/layout/webrtc_call_view.xml @@ -69,11 +69,11 @@ android:id="@+id/call_screen_pip" android:layout_width="@dimen/picture_in_picture_gesture_helper_pip_width" android:layout_height="@dimen/picture_in_picture_gesture_helper_pip_height" + android:background="@null" android:clipChildren="true" android:translationX="100000dp" android:translationY="-100000dp" android:visibility="gone" - android:background="@null" app:cardCornerRadius="8dp" tools:background="@color/red" tools:visibility="visible"> @@ -142,11 +142,13 @@ android:layout_height="56dp" android:layout_marginEnd="16dp" android:layout_marginBottom="34dp" + 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:srcCompat="@drawable/webrtc_call_screen_speaker_toggle" + tools:visibility="visible" /> + app:layout_constraintStart_toEndOf="@id/call_screen_speaker_toggle" + tools:visibility="visible" /> + app:layout_constraintStart_toEndOf="@id/call_screen_video_toggle" + tools:visibility="visible" /> + app:layout_constraintStart_toEndOf="@id/call_screen_mic_toggle" + tools:visibility="visible" /> + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/call_screen_decline_call" + tools:visibility="visible" /> + app:layout_constraintStart_toEndOf="@id/call_screen_decline_call" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/call_screen_answer_call" + tools:visibility="visible" /> + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> - - - - - - - - + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> \ No newline at end of file