Handle blocked users in group calls.

This commit is contained in:
Cody Henthorne
2020-12-02 13:43:05 -05:00
committed by Greyson Parrelli
parent 01f143667f
commit 050fad3114
13 changed files with 183 additions and 33 deletions

View File

@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms;
import android.Manifest;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -32,6 +33,7 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProviders;
@@ -77,6 +79,12 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
private WebRtcCallViewModel viewModel;
private boolean enableVideoIfAvailable;
@Override
protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
super.attachBaseContext(newBase);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");

View File

@@ -4,16 +4,22 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
@@ -39,12 +45,18 @@ public class CallParticipantView extends ConstraintLayout {
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 AvatarImageView avatar;
private TextureViewRenderer renderer;
private ImageView pipAvatar;
private ContactPhoto contactPhoto;
private View audioMuted;
private View infoOverlay;
private EmojiTextView infoMessage;
private Button infoMoreInfo;
private AppCompatImageView infoIcon;
public CallParticipantView(@NonNull Context context) {
super(context);
@@ -62,10 +74,14 @@ public class CallParticipantView extends ConstraintLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
avatar = findViewById(R.id.call_participant_item_avatar);
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
renderer = findViewById(R.id.call_participant_renderer);
audioMuted = findViewById(R.id.call_participant_mic_muted);
avatar = findViewById(R.id.call_participant_item_avatar);
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
renderer = findViewById(R.id.call_participant_renderer);
audioMuted = findViewById(R.id.call_participant_mic_muted);
infoOverlay = findViewById(R.id.call_participant_info_overlay);
infoIcon = findViewById(R.id.call_participant_info_icon);
infoMessage = findViewById(R.id.call_participant_info_message);
infoMoreInfo = findViewById(R.id.call_participant_info_more_info);
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
useLargeAvatar();
@@ -82,16 +98,43 @@ public class CallParticipantView extends ConstraintLayout {
void setCallParticipant(@NonNull CallParticipant participant) {
boolean participantChanged = recipientId == null || !recipientId.equals(participant.getRecipient().getId());
recipientId = participant.getRecipient().getId();
infoMode = participant.getRecipient().isBlocked() || !participant.isMediaKeysReceived();
renderer.setVisibility(participant.isVideoEnabled() ? View.VISIBLE : View.GONE);
if (participant.isVideoEnabled()) {
if (participant.getVideoSink().getEglBase() != null) {
renderer.init(participant.getVideoSink().getEglBase());
}
renderer.attachBroadcastVideoSink(participant.getVideoSink());
} else {
if (infoMode) {
renderer.setVisibility(View.GONE);
renderer.attachBroadcastVideoSink(null);
audioMuted.setVisibility(View.GONE);
avatar.setVisibility(View.GONE);
pipAvatar.setVisibility(View.GONE);
infoOverlay.setVisibility(View.VISIBLE);
ImageViewCompat.setImageTintList(infoIcon, ContextCompat.getColorStateList(getContext(), R.color.core_white));
if (participant.getRecipient().isBlocked()) {
infoIcon.setImageResource(R.drawable.ic_block_tinted_24);
infoMessage.setText(getContext().getString(R.string.CallParticipantView__s_is_blocked, participant.getRecipient().getShortDisplayName(getContext())));
infoMoreInfo.setOnClickListener(v -> showBlockedDialog(participant.getRecipient()));
} else {
infoIcon.setImageResource(R.drawable.ic_error_solid_24);
infoMessage.setText(getContext().getString(R.string.CallParticipantView__cant_receive_audio_video_from_s, participant.getRecipient().getShortDisplayName(getContext())));
infoMoreInfo.setOnClickListener(v -> showNoMediaKeysDialog(participant.getRecipient()));
}
} else {
infoOverlay.setVisibility(View.GONE);
renderer.setVisibility(participant.isVideoEnabled() ? View.VISIBLE : View.GONE);
if (participant.isVideoEnabled()) {
if (participant.getVideoSink().getEglBase() != null) {
renderer.init(participant.getVideoSink().getEglBase());
}
renderer.attachBroadcastVideoSink(participant.getVideoSink());
} else {
renderer.attachBroadcastVideoSink(null);
}
audioMuted.setVisibility(participant.isMicrophoneEnabled() ? View.GONE : View.VISIBLE);
}
if (participantChanged || !Objects.equals(contactPhoto, participant.getRecipient().getContactPhoto())) {
@@ -100,11 +143,15 @@ public class CallParticipantView extends ConstraintLayout {
setPipAvatar(participant.getRecipient());
contactPhoto = participant.getRecipient().getContactPhoto();
}
audioMuted.setVisibility(participant.isMicrophoneEnabled() ? View.GONE : View.VISIBLE);
}
void setRenderInPip(boolean shouldRenderInPip) {
if (infoMode) {
infoMessage.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
infoMoreInfo.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
return;
}
avatar.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
pipAvatar.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
}
@@ -146,6 +193,22 @@ public class CallParticipantView extends ConstraintLayout {
pipAvatar.setBackgroundColor(recipient.getColor().toActionBarColor(getContext()));
}
private void showBlockedDialog(@NonNull Recipient recipient) {
new AlertDialog.Builder(getContext())
.setTitle(getContext().getString(R.string.CallParticipantView__s_is_blocked, recipient.getShortDisplayName(getContext())))
.setMessage(R.string.CallParticipantView__you_wont_receive_their_audio_or_video)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void showNoMediaKeysDialog(@NonNull Recipient recipient) {
new AlertDialog.Builder(getContext())
.setTitle(getContext().getString(R.string.CallParticipantView__cant_receive_audio_and_video_from_s, recipient.getShortDisplayName(getContext())))
.setMessage(R.string.CallParticipantView__this_may_be_Because_they_have_not_verified_your_safety_number_change)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {

View File

@@ -10,9 +10,9 @@ import org.whispersystems.libsignal.IdentityKey;
import java.util.Objects;
public class CallParticipant {
public final class CallParticipant {
public static final CallParticipant EMPTY = createRemote(Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0);
public static final CallParticipant EMPTY = createRemote(Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0, true);
private final @NonNull CameraState cameraState;
private final @NonNull Recipient recipient;
@@ -21,6 +21,7 @@ public class CallParticipant {
private final boolean videoEnabled;
private final boolean microphoneEnabled;
private final long lastSpoke;
private final boolean mediaKeysReceived;
public static @NonNull CallParticipant createLocal(@NonNull CameraState cameraState,
@NonNull BroadcastVideoSink renderer,
@@ -32,7 +33,8 @@ public class CallParticipant {
cameraState,
cameraState.isEnabled() && cameraState.getCameraCount() > 0,
microphoneEnabled,
0);
0,
true);
}
public static @NonNull CallParticipant createRemote(@NonNull Recipient recipient,
@@ -40,9 +42,10 @@ public class CallParticipant {
@NonNull BroadcastVideoSink renderer,
boolean audioEnabled,
boolean videoEnabled,
long lastSpoke)
long lastSpoke,
boolean mediaKeysReceived)
{
return new CallParticipant(recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke);
return new CallParticipant(recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke, mediaKeysReceived);
}
private CallParticipant(@NonNull Recipient recipient,
@@ -51,7 +54,8 @@ public class CallParticipant {
@NonNull CameraState cameraState,
boolean videoEnabled,
boolean microphoneEnabled,
long lastSpoke)
long lastSpoke,
boolean mediaKeysReceived)
{
this.recipient = recipient;
this.identityKey = identityKey;
@@ -60,14 +64,15 @@ public class CallParticipant {
this.videoEnabled = videoEnabled;
this.microphoneEnabled = microphoneEnabled;
this.lastSpoke = lastSpoke;
this.mediaKeysReceived = mediaKeysReceived;
}
public @NonNull CallParticipant withIdentityKey(@NonNull IdentityKey identityKey) {
return new CallParticipant(recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke);
return new CallParticipant(recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived);
}
public @NonNull CallParticipant withVideoEnabled(boolean videoEnabled) {
return new CallParticipant(recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke);
return new CallParticipant(recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived);
}
public @NonNull Recipient getRecipient() {
@@ -109,6 +114,10 @@ public class CallParticipant {
return lastSpoke;
}
public boolean isMediaKeysReceived() {
return mediaKeysReceived;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -117,6 +126,7 @@ public class CallParticipant {
return videoEnabled == that.videoEnabled &&
microphoneEnabled == that.microphoneEnabled &&
lastSpoke == that.lastSpoke &&
mediaKeysReceived == that.mediaKeysReceived &&
cameraState.equals(that.cameraState) &&
recipient.equals(that.recipient) &&
Objects.equals(identityKey, that.identityKey) &&
@@ -125,7 +135,7 @@ public class CallParticipant {
@Override
public int hashCode() {
return Objects.hash(cameraState, recipient, identityKey, videoSink, videoEnabled, microphoneEnabled, lastSpoke);
return Objects.hash(cameraState, recipient, identityKey, videoSink, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived);
}
@Override
@@ -137,6 +147,8 @@ public class CallParticipant {
", videoSink=" + (videoSink.getEglBase() == null ? "not initialized" : "initialized") +
", videoEnabled=" + videoEnabled +
", microphoneEnabled=" + microphoneEnabled +
", lastSpoke=" + lastSpoke +
", mediaKeysReceived=" + mediaKeysReceived +
'}';
}
}

View File

@@ -58,6 +58,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
List<RecipientId> recipients = Stream.of(RecipientUtil.getEligibleForSending(conversationRecipient.getParticipants()))
.filterNot(Recipient::isSelf)
.filterNot(Recipient::isBlocked)
.map(Recipient::getId)
.toList();

View File

@@ -444,6 +444,10 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
{
Callable<Boolean> callable = () -> {
Recipient recipient = remotePeer.getRecipient();
if (recipient.isBlocked()) {
return true;
}
messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(WebRtcCallService.this, recipient),
UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient),
callMessage);

View File

@@ -46,7 +46,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
true,
false,
0
0,
true
))
.build();
@@ -86,7 +87,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
true,
false,
0
0,
true
))
.build();
}

View File

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

View File

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