mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 11:51:10 +01:00
Handle safety number changes in a group call context.
This commit is contained in:
committed by
Greyson Parrelli
parent
112782ccaf
commit
42d61518b3
@@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
/**
|
||||
* Utility for showing and hiding safety number change notifications during a group call.
|
||||
*/
|
||||
public final class GroupCallSafetyNumberChangeNotificationUtil {
|
||||
|
||||
public static final String GROUP_CALLING_NOTIFICATION_TAG = "group_calling";
|
||||
|
||||
private GroupCallSafetyNumberChangeNotificationUtil() {
|
||||
}
|
||||
|
||||
public static void showNotification(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
|
||||
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
|
||||
Notification safetyNumberChangeNotification = new NotificationCompat.Builder(context, NotificationChannels.CALLS)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(recipient.getDisplayName(context))
|
||||
.setContentText(context.getString(R.string.GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed))
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed)))
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
|
||||
NotificationManagerCompat.from(context).notify(GROUP_CALLING_NOTIFICATION_TAG, recipient.hashCode(), safetyNumberChangeNotification);
|
||||
}
|
||||
|
||||
public static void cancelNotification(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
NotificationManagerCompat.from(context).cancel(GROUP_CALLING_NOTIFICATION_TAG, recipient.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,35 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class WebRtcCallRepository {
|
||||
|
||||
private final Context context;
|
||||
private final AudioManager audioManager;
|
||||
|
||||
WebRtcCallRepository() {
|
||||
WebRtcCallRepository(@NonNull Context context) {
|
||||
this.context = context;
|
||||
this.audioManager = ServiceUtil.getAudioManager(ApplicationDependencies.getApplication());
|
||||
}
|
||||
|
||||
WebRtcAudioOutput getAudioOutput() {
|
||||
@NonNull WebRtcAudioOutput getAudioOutput() {
|
||||
if (audioManager.isBluetoothScoOn()) {
|
||||
return WebRtcAudioOutput.HEADSET;
|
||||
} else if (audioManager.isSpeakerphoneOn()) {
|
||||
@@ -22,4 +38,20 @@ class WebRtcCallRepository {
|
||||
return WebRtcAudioOutput.HANDSET;
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void getIdentityRecords(@NonNull Recipient recipient, @NonNull Consumer<IdentityRecordList> consumer) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
List<Recipient> recipients;
|
||||
|
||||
if (recipient.isGroup()) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
||||
} else {
|
||||
recipients = Collections.singletonList(recipient);
|
||||
}
|
||||
|
||||
consumer.accept(identityDatabase.getIdentities(recipients));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,8 +689,4 @@ public class WebRtcCallView extends FrameLayout {
|
||||
void onShowParticipantsList();
|
||||
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onPotentialLayoutChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,19 @@ import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,6 +39,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
|
||||
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
|
||||
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
|
||||
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
|
||||
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
|
||||
|
||||
private boolean canDisplayTooltipIfNeeded = true;
|
||||
private boolean hasEnabledLocalVideo = false;
|
||||
@@ -44,8 +50,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private Runnable elapsedTimeRunnable = this::handleTick;
|
||||
private boolean canEnterPipMode = false;
|
||||
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
|
||||
private boolean callingStarted = false;
|
||||
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository();
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
|
||||
|
||||
public LiveData<Boolean> getMicrophoneEnabled() {
|
||||
return Transformations.distinctUntilChanged(microphoneEnabled);
|
||||
@@ -79,6 +86,10 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
return callParticipantListUpdate;
|
||||
}
|
||||
|
||||
public LiveData<SafetyNumberChangeEvent> getSafetyNumberChangeEvent() {
|
||||
return safetyNumberChangeEvent;
|
||||
}
|
||||
|
||||
public boolean canEnterPipMode() {
|
||||
return canEnterPipMode;
|
||||
}
|
||||
@@ -87,6 +98,10 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
return answerWithVideoAvailable;
|
||||
}
|
||||
|
||||
public boolean isCallingStarted() {
|
||||
return callingStarted;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setIsInPipMode(boolean isInPipMode) {
|
||||
this.isInPipMode.setValue(isInPipMode);
|
||||
@@ -123,6 +138,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
previousParticipantsList = webRtcViewModel.getRemoteParticipants();
|
||||
|
||||
identityChangedRecipients.setValue(webRtcViewModel.getIdentityChangedParticipants());
|
||||
}
|
||||
|
||||
updateWebRtcControls(webRtcViewModel.getState(),
|
||||
@@ -146,13 +163,13 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
if (localParticipant.getCameraState().isEnabled()) {
|
||||
canDisplayTooltipIfNeeded = false;
|
||||
hasEnabledLocalVideo = true;
|
||||
events.setValue(Event.DISMISS_VIDEO_TOOLTIP);
|
||||
events.setValue(new Event.DismissVideoTooltip());
|
||||
}
|
||||
|
||||
// If remote video is enabled and we a) haven't shown our video and b) have not dismissed the popup
|
||||
if (canDisplayTooltipIfNeeded && webRtcViewModel.isRemoteVideoEnabled() && !hasEnabledLocalVideo) {
|
||||
canDisplayTooltipIfNeeded = false;
|
||||
events.setValue(Event.SHOW_VIDEO_TOOLTIP);
|
||||
events.setValue(new Event.ShowVideoTooltip());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,8 +276,74 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
cancelTimer();
|
||||
}
|
||||
|
||||
public enum Event {
|
||||
SHOW_VIDEO_TOOLTIP,
|
||||
DISMISS_VIDEO_TOOLTIP
|
||||
public void startCall(boolean isVideoCall) {
|
||||
callingStarted = true;
|
||||
Recipient recipient = getRecipient().get();
|
||||
if (recipient.isGroup()) {
|
||||
repository.getIdentityRecords(recipient, identityRecords -> {
|
||||
if (identityRecords.isUntrusted(false) || identityRecords.isUnverified(false)) {
|
||||
List<IdentityDatabase.IdentityRecord> records = identityRecords.getUnverifiedRecords();
|
||||
records.addAll(identityRecords.getUntrustedRecords());
|
||||
events.postValue(new Event.ShowGroupCallSafetyNumberChange(records));
|
||||
} else {
|
||||
events.postValue(new Event.StartCall(isVideoCall));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
events.postValue(new Event.StartCall(isVideoCall));
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Event {
|
||||
private Event() {
|
||||
}
|
||||
|
||||
public static class ShowVideoTooltip extends Event {
|
||||
}
|
||||
|
||||
public static class DismissVideoTooltip extends Event {
|
||||
}
|
||||
|
||||
public static class StartCall extends Event {
|
||||
private final boolean isVideoCall;
|
||||
|
||||
public StartCall(boolean isVideoCall) {
|
||||
this.isVideoCall = isVideoCall;
|
||||
}
|
||||
|
||||
public boolean isVideoCall() {
|
||||
return isVideoCall;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ShowGroupCallSafetyNumberChange extends Event {
|
||||
private final List<IdentityDatabase.IdentityRecord> identityRecords;
|
||||
|
||||
public ShowGroupCallSafetyNumberChange(@NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
|
||||
this.identityRecords = identityRecords;
|
||||
}
|
||||
|
||||
public @NonNull List<IdentityDatabase.IdentityRecord> getIdentityRecords() {
|
||||
return identityRecords;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SafetyNumberChangeEvent {
|
||||
private final boolean isInPipMode;
|
||||
private final Collection<RecipientId> recipientIds;
|
||||
|
||||
private SafetyNumberChangeEvent(boolean isInPipMode, @NonNull Collection<RecipientId> recipientIds) {
|
||||
this.isInPipMode = isInPipMode;
|
||||
this.recipientIds = recipientIds;
|
||||
}
|
||||
|
||||
public boolean isInPipMode() {
|
||||
return isInPipMode;
|
||||
}
|
||||
|
||||
public @NonNull Collection<RecipientId> getRecipientIds() {
|
||||
return recipientIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user