Fix a few minor group call UI issues.

This commit is contained in:
Cody Henthorne 2020-12-07 10:05:35 -05:00 committed by GitHub
parent 2dcc7d284f
commit bdc6c8c65a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 73 additions and 46 deletions

View file

@ -59,8 +59,8 @@ public class BroadcastVideoSink implements VideoSink {
} }
public @NonNull RequestedSize getMaxRequestingSize() { public @NonNull RequestedSize getMaxRequestingSize() {
int width = 0; int width = 1;
int height = 0; int height = 1;
synchronized (requestingSizes) { synchronized (requestingSizes) {
for (Point size : requestingSizes.values()) { for (Point size : requestingSizes.values()) {

View file

@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.webrtc.RendererCommon; import org.webrtc.RendererCommon;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
/** /**
* Encapsulates views needed to show a call participant including their * Encapsulates views needed to show a call participant including their
@ -42,12 +43,14 @@ public class CallParticipantView extends ConstraintLayout {
private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider(); private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
private static final int SMALL_AVATAR = ViewUtil.dpToPx(96); private static final long DELAY_SHOWING_MISSING_MEDIA_KEYS = TimeUnit.SECONDS.toMillis(5);
private static final int LARGE_AVATAR = ViewUtil.dpToPx(112); private static final int SMALL_AVATAR = ViewUtil.dpToPx(96);
private static final int LARGE_AVATAR = ViewUtil.dpToPx(112);
private RecipientId recipientId; private RecipientId recipientId;
private boolean infoMode; private boolean infoMode;
private AppCompatImageView backgroundAvatar;
private AvatarImageView avatar; private AvatarImageView avatar;
private TextureViewRenderer renderer; private TextureViewRenderer renderer;
private ImageView pipAvatar; private ImageView pipAvatar;
@ -74,14 +77,15 @@ public class CallParticipantView extends ConstraintLayout {
@Override @Override
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
avatar = findViewById(R.id.call_participant_item_avatar); backgroundAvatar = findViewById(R.id.call_participant_background_avatar);
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar); avatar = findViewById(R.id.call_participant_item_avatar);
renderer = findViewById(R.id.call_participant_renderer); pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
audioMuted = findViewById(R.id.call_participant_mic_muted); renderer = findViewById(R.id.call_participant_renderer);
infoOverlay = findViewById(R.id.call_participant_info_overlay); audioMuted = findViewById(R.id.call_participant_mic_muted);
infoIcon = findViewById(R.id.call_participant_info_icon); infoOverlay = findViewById(R.id.call_participant_info_overlay);
infoMessage = findViewById(R.id.call_participant_info_message); infoIcon = findViewById(R.id.call_participant_info_icon);
infoMoreInfo = findViewById(R.id.call_participant_info_more_info); infoMessage = findViewById(R.id.call_participant_info_message);
infoMoreInfo = findViewById(R.id.call_participant_info_more_info);
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER); avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
useLargeAvatar(); useLargeAvatar();
@ -98,7 +102,7 @@ public class CallParticipantView extends ConstraintLayout {
void setCallParticipant(@NonNull CallParticipant participant) { void setCallParticipant(@NonNull CallParticipant participant) {
boolean participantChanged = recipientId == null || !recipientId.equals(participant.getRecipient().getId()); boolean participantChanged = recipientId == null || !recipientId.equals(participant.getRecipient().getId());
recipientId = participant.getRecipient().getId(); recipientId = participant.getRecipient().getId();
infoMode = participant.getRecipient().isBlocked() || !participant.isMediaKeysReceived(); infoMode = participant.getRecipient().isBlocked() || (!participant.isMediaKeysReceived() && (System.currentTimeMillis() - participant.getAddedToCallTime()) > DELAY_SHOWING_MISSING_MEDIA_KEYS);
if (infoMode) { if (infoMode) {
renderer.setVisibility(View.GONE); renderer.setVisibility(View.GONE);
@ -139,7 +143,7 @@ public class CallParticipantView extends ConstraintLayout {
if (participantChanged || !Objects.equals(contactPhoto, participant.getRecipient().getContactPhoto())) { if (participantChanged || !Objects.equals(contactPhoto, participant.getRecipient().getContactPhoto())) {
avatar.setAvatarUsingProfile(participant.getRecipient()); avatar.setAvatarUsingProfile(participant.getRecipient());
AvatarUtil.loadBlurredIconIntoViewBackground(participant.getRecipient(), this, true); AvatarUtil.loadBlurredIconIntoImageView(participant.getRecipient(), backgroundAvatar);
setPipAvatar(participant.getRecipient()); setPipAvatar(participant.getRecipient());
contactPhoto = participant.getRecipient().getContactPhoto(); contactPhoto = participant.getRecipient().getContactPhoto();
} }

View file

@ -60,6 +60,7 @@ class WebRtcCallParticipantsRecyclerAdapter extends ListAdapter<CallParticipant,
@Override @Override
void bind(@NonNull CallParticipant callParticipant) { void bind(@NonNull CallParticipant callParticipant) {
callParticipantView.setCallParticipant(callParticipant); callParticipantView.setCallParticipant(callParticipant);
callParticipantView.setRenderInPip(true);
} }
} }

View file

@ -12,7 +12,7 @@ import java.util.Objects;
public final class CallParticipant { public final class CallParticipant {
public static final CallParticipant EMPTY = createRemote(new CallParticipantId(Recipient.UNKNOWN), Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0, true); public static final CallParticipant EMPTY = createRemote(new CallParticipantId(Recipient.UNKNOWN), Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0, true, 0);
private final @NonNull CallParticipantId callParticipantId; private final @NonNull CallParticipantId callParticipantId;
private final @NonNull CameraState cameraState; private final @NonNull CameraState cameraState;
@ -23,6 +23,7 @@ public final class CallParticipant {
private final boolean microphoneEnabled; private final boolean microphoneEnabled;
private final long lastSpoke; private final long lastSpoke;
private final boolean mediaKeysReceived; private final boolean mediaKeysReceived;
private final long addedToCallTime;
public static @NonNull CallParticipant createLocal(@NonNull CameraState cameraState, public static @NonNull CallParticipant createLocal(@NonNull CameraState cameraState,
@NonNull BroadcastVideoSink renderer, @NonNull BroadcastVideoSink renderer,
@ -36,7 +37,8 @@ public final class CallParticipant {
cameraState.isEnabled() && cameraState.getCameraCount() > 0, cameraState.isEnabled() && cameraState.getCameraCount() > 0,
microphoneEnabled, microphoneEnabled,
0, 0,
true); true,
0);
} }
public static @NonNull CallParticipant createRemote(@NonNull CallParticipantId callParticipantId, public static @NonNull CallParticipant createRemote(@NonNull CallParticipantId callParticipantId,
@ -46,9 +48,10 @@ public final class CallParticipant {
boolean audioEnabled, boolean audioEnabled,
boolean videoEnabled, boolean videoEnabled,
long lastSpoke, long lastSpoke,
boolean mediaKeysReceived) boolean mediaKeysReceived,
long addedToCallTime)
{ {
return new CallParticipant(callParticipantId, recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke, mediaKeysReceived); return new CallParticipant(callParticipantId, recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
} }
private CallParticipant(@NonNull CallParticipantId callParticipantId, private CallParticipant(@NonNull CallParticipantId callParticipantId,
@ -59,7 +62,8 @@ public final class CallParticipant {
boolean videoEnabled, boolean videoEnabled,
boolean microphoneEnabled, boolean microphoneEnabled,
long lastSpoke, long lastSpoke,
boolean mediaKeysReceived) boolean mediaKeysReceived,
long addedToCallTime)
{ {
this.callParticipantId = callParticipantId; this.callParticipantId = callParticipantId;
this.recipient = recipient; this.recipient = recipient;
@ -70,14 +74,15 @@ public final class CallParticipant {
this.microphoneEnabled = microphoneEnabled; this.microphoneEnabled = microphoneEnabled;
this.lastSpoke = lastSpoke; this.lastSpoke = lastSpoke;
this.mediaKeysReceived = mediaKeysReceived; this.mediaKeysReceived = mediaKeysReceived;
this.addedToCallTime = addedToCallTime;
} }
public @NonNull CallParticipant withIdentityKey(@NonNull IdentityKey identityKey) { public @NonNull CallParticipant withIdentityKey(@NonNull IdentityKey identityKey) {
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived); return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
} }
public @NonNull CallParticipant withVideoEnabled(boolean videoEnabled) { public @NonNull CallParticipant withVideoEnabled(boolean videoEnabled) {
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived); return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
} }
public @NonNull CallParticipantId getCallParticipantId() { public @NonNull CallParticipantId getCallParticipantId() {
@ -127,6 +132,10 @@ public final class CallParticipant {
return mediaKeysReceived; return mediaKeysReceived;
} }
public long getAddedToCallTime() {
return addedToCallTime;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -137,6 +146,7 @@ public final class CallParticipant {
microphoneEnabled == that.microphoneEnabled && microphoneEnabled == that.microphoneEnabled &&
lastSpoke == that.lastSpoke && lastSpoke == that.lastSpoke &&
mediaKeysReceived == that.mediaKeysReceived && mediaKeysReceived == that.mediaKeysReceived &&
addedToCallTime == that.addedToCallTime &&
cameraState.equals(that.cameraState) && cameraState.equals(that.cameraState) &&
recipient.equals(that.recipient) && recipient.equals(that.recipient) &&
Objects.equals(identityKey, that.identityKey) && Objects.equals(identityKey, that.identityKey) &&
@ -145,7 +155,7 @@ public final class CallParticipant {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(callParticipantId, cameraState, recipient, identityKey, videoSink, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived); return Objects.hash(callParticipantId, cameraState, recipient, identityKey, videoSink, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
} }
@Override @Override
@ -159,6 +169,7 @@ public final class CallParticipant {
", microphoneEnabled=" + microphoneEnabled + ", microphoneEnabled=" + microphoneEnabled +
", lastSpoke=" + lastSpoke + ", lastSpoke=" + lastSpoke +
", mediaKeysReceived=" + mediaKeysReceived + ", mediaKeysReceived=" + mediaKeysReceived +
", addedToCallTime=" + addedToCallTime +
'}'; '}';
} }
} }

View file

@ -49,7 +49,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
true, true,
false, false,
0, 0,
true true,
0
)) ))
.build(); .build();
@ -91,7 +92,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
true, true,
false, false,
0, 0,
true true,
0
)) ))
.build(); .build();
} }

View file

@ -92,7 +92,8 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
Boolean.FALSE.equals(device.getAudioMuted()), Boolean.FALSE.equals(device.getAudioMuted()),
Boolean.FALSE.equals(device.getVideoMuted()), Boolean.FALSE.equals(device.getVideoMuted()),
device.getSpeakerTime(), device.getSpeakerTime(),
device.getMediaKeysReceived())); device.getMediaKeysReceived(),
device.getAddedTime()));
} }
return builder.build(); return builder.build();

View file

@ -115,7 +115,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
.changeCallInfoState(); .changeCallInfoState();
for (Recipient recipient : callParticipants) { for (Recipient recipient : callParticipants) {
builder.putParticipant(recipient, CallParticipant.createRemote(new CallParticipantId(recipient), recipient, null, new BroadcastVideoSink(null), true, true, 0, false)); builder.putParticipant(recipient, CallParticipant.createRemote(new CallParticipantId(recipient), recipient, null, new BroadcastVideoSink(null), true, true, 0, false, 0));
} }
return builder.build(); return builder.build();

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.text.TextUtils; import android.text.TextUtils;
@ -12,15 +13,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.IconCompat; import androidx.core.graphics.drawable.IconCompat;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.request.target.CustomViewTarget; import com.bumptech.glide.request.target.CustomViewTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
@ -36,23 +36,18 @@ import java.util.concurrent.ExecutionException;
public final class AvatarUtil { public final class AvatarUtil {
private static final String TAG = Log.tag(AvatarUtil.class);
private AvatarUtil() { private AvatarUtil() {
} }
public static void loadBlurredIconIntoViewBackground(@NonNull Recipient recipient, @NonNull View target) { public static void loadBlurredIconIntoImageView(@NonNull Recipient recipient, @NonNull AppCompatImageView target) {
loadBlurredIconIntoViewBackground(recipient, target, false);
}
public static void loadBlurredIconIntoViewBackground(@NonNull Recipient recipient, @NonNull View target, boolean useSelfProfileAvatar) {
Context context = target.getContext(); Context context = target.getContext();
ContactPhoto photo; ContactPhoto photo;
if (recipient.isSelf() && useSelfProfileAvatar) { if (recipient.isSelf()) {
photo = new ProfileContactPhoto(Recipient.self(), Recipient.self().getProfileAvatar()); photo = new ProfileContactPhoto(Recipient.self(), Recipient.self().getProfileAvatar());
} else if (recipient.getContactPhoto() == null) { } else if (recipient.getContactPhoto() == null) {
target.setImageDrawable(null);
target.setBackgroundColor(ContextCompat.getColor(target.getContext(), R.color.black)); target.setBackgroundColor(ContextCompat.getColor(target.getContext(), R.color.black));
return; return;
} else { } else {
@ -61,21 +56,22 @@ public final class AvatarUtil {
GlideApp.with(target) GlideApp.with(target)
.load(photo) .load(photo)
.transform(new CenterCrop(), new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS)) .transform(new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS))
.into(new CustomViewTarget<View, Drawable>(target) { .into(new CustomViewTarget<View, Drawable>(target) {
@Override @Override
public void onLoadFailed(@Nullable Drawable errorDrawable) { public void onLoadFailed(@Nullable Drawable errorDrawable) {
target.setImageDrawable(null);
target.setBackgroundColor(ContextCompat.getColor(target.getContext(), R.color.black)); target.setBackgroundColor(ContextCompat.getColor(target.getContext(), R.color.black));
} }
@Override @Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
target.setBackground(resource); target.setImageDrawable(resource);
} }
@Override @Override
protected void onResourceCleared(@Nullable Drawable placeholder) { protected void onResourceCleared(@Nullable Drawable placeholder) {
target.setBackground(placeholder); target.setImageDrawable(placeholder);
} }
}); });
} }

View file

@ -7,6 +7,17 @@
tools:layout_height="match_parent" tools:layout_height="match_parent"
tools:layout_width="match_parent"> tools:layout_width="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_background_avatar"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic" />
<org.thoughtcrime.securesms.components.AvatarImageView <org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/call_participant_item_avatar" android:id="@+id/call_participant_item_avatar"
android:layout_width="112dp" android:layout_width="112dp"
@ -51,17 +62,16 @@
<LinearLayout <LinearLayout
android:id="@+id/call_participant_info_overlay" android:id="@+id/call_participant_info_overlay"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:background="@color/transparent_black_40" android:background="@color/transparent_black_40"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"> tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
@ -75,7 +85,9 @@
android:id="@+id/call_participant_info_message" android:id="@+id/call_participant_info_message"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:lines="3" android:lines="3"

View file

@ -172,7 +172,7 @@ public class CallParticipantListUpdateTest {
private static CallParticipant createParticipant(long recipientId, long deMuxId) { private static CallParticipant createParticipant(long recipientId, long deMuxId) {
Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true); Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true);
return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(null), false, false, -1, false); return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(null), false, false, -1, false, 0);
} }
} }