Ensure rate limit dialog appears during calls.
This commit is contained in:
parent
6673293e29
commit
9fa04e03fd
11 changed files with 253 additions and 91 deletions
|
@ -91,8 +91,11 @@ import org.thoughtcrime.securesms.components.webrtc.v2.CallPermissionsDialogCont
|
|||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment;
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaRequiredEvent;
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -116,7 +119,6 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
|||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -127,7 +129,7 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||
|
||||
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||
|
||||
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback, ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
|
||||
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback, ReactWithAnyEmojiBottomSheetDialogFragment.Callback, RecaptchaProofBottomSheetFragment.Callback {
|
||||
|
||||
private static final String TAG = Log.tag(WebRtcCallActivity.class);
|
||||
|
||||
|
@ -263,6 +265,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
public void onResume() {
|
||||
Log.i(TAG, "onResume()");
|
||||
super.onResume();
|
||||
EventBus.getDefault().register(this);
|
||||
|
||||
initializeScreenshotSecurity();
|
||||
|
||||
if (!EventBus.getDefault().isRegistered(this)) {
|
||||
|
@ -287,6 +291,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
enterPipOnResume = false;
|
||||
enterPipModeIfPossible();
|
||||
}
|
||||
|
||||
if (SignalStore.rateLimit().needsRecaptcha()) {
|
||||
RecaptchaProofBottomSheetFragment.show(getSupportFragmentManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -303,6 +311,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
Log.i(TAG, "onPause");
|
||||
super.onPause();
|
||||
|
||||
EventBus.getDefault().unregister(this);
|
||||
|
||||
if (!callPermissionsDialogController.isAskingForPermission() && !viewModel.isCallStarting() && !isChangingConfigurations()) {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot();
|
||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
|
@ -345,6 +355,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onRecaptchaRequiredEvent(RecaptchaRequiredEvent recaptchaRequiredEvent) {
|
||||
RecaptchaProofBottomSheetFragment.show(getSupportFragmentManager());
|
||||
}
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
|
@ -1071,6 +1086,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
callOverflowPopupWindow.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProofCompleted() {
|
||||
AppDependencies.getSignalCallManager().resendMediaKeys();
|
||||
}
|
||||
|
||||
private final class ControlsListener implements WebRtcCallView.ControlsListener {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
||||
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -215,7 +216,12 @@ public class IndividualSendJob extends PushSendJob {
|
|||
database.markAsSentFailed(messageId);
|
||||
RetrieveProfileJob.enqueue(recipientId);
|
||||
} catch (ProofRequiredException e) {
|
||||
handleProofRequiredException(context, e, SignalDatabase.threads().getRecipientForThreadId(threadId), threadId, messageId, true);
|
||||
ProofRequiredExceptionHandler.Result result = ProofRequiredExceptionHandler.handle(context, e, SignalDatabase.threads().getRecipientForThreadId(threadId), threadId, messageId);
|
||||
if (result.isRetry()) {
|
||||
throw new RetryLaterException();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
SignalLocalMetrics.IndividualMessageSend.onJobFinished(messageId);
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint;
|
|||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendUtil;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -22,6 +23,7 @@ import org.whispersystems.signalservice.api.crypto.ContentHint;
|
|||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -170,7 +172,8 @@ public class ProfileKeySendJob extends BaseJob {
|
|||
.withTimestamp(System.currentTimeMillis())
|
||||
.withProfileKey(Recipient.self().resolve().getProfileKey());
|
||||
|
||||
List<SendMessageResult> results = GroupSendUtil.sendUnresendableDataMessage(context, null, destinations, false, ContentHint.IMPLICIT, dataMessage.build(), false);
|
||||
List<SendMessageResult> results = GroupSendUtil.sendUnresendableDataMessage(context, null, destinations, false, ContentHint.IMPLICIT, dataMessage.build(), false);
|
||||
ProofRequiredException proofRequired = Stream.of(results).filter(r -> r.getProofRequiredFailure() != null).findLast().map(SendMessageResult::getProofRequiredFailure).orElse(null);
|
||||
|
||||
GroupSendJobHelper.SendResult groupResult = GroupSendJobHelper.getCompletedSends(destinations, results);
|
||||
|
||||
|
@ -178,6 +181,11 @@ public class ProfileKeySendJob extends BaseJob {
|
|||
SignalDatabase.recipients().markUnregistered(unregistered);
|
||||
}
|
||||
|
||||
if (proofRequired != null) {
|
||||
Log.d(TAG, "Notifying the user they were rate limited.");
|
||||
ProofRequiredExceptionHandler.handle(context, proofRequired, null, -1L, -1L);
|
||||
}
|
||||
|
||||
return groupResult.completed;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.messages.StorySendUtil;
|
|||
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
||||
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -456,7 +457,12 @@ public final class PushGroupSendJob extends PushSendJob {
|
|||
SignalDatabase.groupReceipts().setUnidentified(successUnidentifiedStatus, messageId);
|
||||
|
||||
if (proofRequired != null) {
|
||||
handleProofRequiredException(context, proofRequired, groupRecipient, threadId, messageId, true);
|
||||
ProofRequiredExceptionHandler.Result result = ProofRequiredExceptionHandler.handle(context, proofRequired, groupRecipient, threadId, messageId);
|
||||
if (result.isRetry()) {
|
||||
throw new RetryLaterException();
|
||||
} else {
|
||||
throw proofRequired;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingNetworkFailures.isEmpty() && existingIdentityMismatches.isEmpty()) {
|
||||
|
|
|
@ -575,84 +575,6 @@ public abstract class PushSendJob extends SendJob {
|
|||
}
|
||||
}
|
||||
|
||||
protected static void handleProofRequiredException(@NonNull Context context, @NonNull ProofRequiredException proofRequired, @Nullable Recipient recipient, long threadId, long messageId, boolean isMms)
|
||||
throws ProofRequiredException, RetryLaterException
|
||||
{
|
||||
Log.w(TAG, "[Proof Required] Options: " + proofRequired.getOptions());
|
||||
|
||||
try {
|
||||
if (proofRequired.getOptions().contains(ProofRequiredException.Option.PUSH_CHALLENGE)) {
|
||||
AppDependencies.getSignalServiceAccountManager().requestRateLimitPushChallenge();
|
||||
Log.i(TAG, "[Proof Required] Successfully requested a challenge. Waiting up to " + PUSH_CHALLENGE_TIMEOUT + " ms.");
|
||||
|
||||
boolean success = new PushChallengeRequest(PUSH_CHALLENGE_TIMEOUT).blockUntilSuccess();
|
||||
|
||||
if (success) {
|
||||
Log.i(TAG, "Successfully responded to a push challenge. Retrying message send.");
|
||||
throw new RetryLaterException(1);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to respond to the push challenge in time. Falling back.");
|
||||
}
|
||||
}
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
Log.w(TAG, "[Proof Required] Could not request a push challenge (" + e.getCode() + "). Falling back.", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "[Proof Required] Network error when requesting push challenge. Retrying later.");
|
||||
throw new RetryLaterException(e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "[Proof Required] Marking message as rate-limited. (id: " + messageId + ", mms: " + isMms + ", thread: " + threadId + ")");
|
||||
if (isMms) {
|
||||
SignalDatabase.messages().markAsRateLimited(messageId);
|
||||
} else {
|
||||
SignalDatabase.messages().markAsRateLimited(messageId);
|
||||
}
|
||||
|
||||
if (proofRequired.getOptions().contains(ProofRequiredException.Option.CAPTCHA)) {
|
||||
Log.i(TAG, "[Proof Required] CAPTCHA required.");
|
||||
SignalStore.rateLimit().markNeedsRecaptcha(proofRequired.getToken());
|
||||
|
||||
if (recipient != null) {
|
||||
ParentStoryId.GroupReply groupReply = SignalDatabase.messages().getParentStoryIdForGroupReply(messageId);
|
||||
AppDependencies.getMessageNotifier().notifyProofRequired(context, recipient, ConversationId.fromThreadAndReply(threadId, groupReply));
|
||||
} else {
|
||||
Log.w(TAG, "[Proof Required] No recipient! Couldn't notify.");
|
||||
}
|
||||
}
|
||||
|
||||
throw proofRequired;
|
||||
}
|
||||
|
||||
protected abstract void onPushSend() throws Exception;
|
||||
|
||||
public static class PushChallengeRequest {
|
||||
private final long timeout;
|
||||
private final CountDownLatch latch;
|
||||
private final EventBus eventBus;
|
||||
|
||||
private PushChallengeRequest(long timeout) {
|
||||
this.timeout = timeout;
|
||||
this.latch = new CountDownLatch(1);
|
||||
this.eventBus = EventBus.getDefault();
|
||||
}
|
||||
|
||||
public boolean blockUntilSuccess() {
|
||||
eventBus.register(this);
|
||||
|
||||
try {
|
||||
return latch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "[Proof Required] Interrupted?", e);
|
||||
return false;
|
||||
} finally {
|
||||
eventBus.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
public void onSuccessReceived(SubmitRateLimitPushChallengeJob.SuccessEvent event) {
|
||||
Log.i(TAG, "[Proof Required] Received a successful result!");
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.ratelimit
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.SubmitRateLimitPushChallengeJob.SuccessEvent
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Reusable ProofRequiredException handling code.
|
||||
*/
|
||||
object ProofRequiredExceptionHandler {
|
||||
|
||||
private val TAG = Log.tag(ProofRequiredExceptionHandler::class)
|
||||
private val PUSH_CHALLENGE_TIMEOUT: Duration = 10.seconds
|
||||
|
||||
/**
|
||||
* Handles the given exception, updating state as necessary.
|
||||
*/
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun handle(context: Context, proofRequired: ProofRequiredException, recipient: Recipient?, threadId: Long, messageId: Long): Result {
|
||||
Log.w(TAG, "[Proof Required] Options: ${proofRequired.options}")
|
||||
|
||||
try {
|
||||
if (ProofRequiredException.Option.PUSH_CHALLENGE in proofRequired.options) {
|
||||
AppDependencies.signalServiceAccountManager.requestRateLimitPushChallenge()
|
||||
Log.i(TAG, "[Proof Required] Successfully requested a challenge. Waiting up to $PUSH_CHALLENGE_TIMEOUT ms.")
|
||||
|
||||
val success = PushChallengeRequest(PUSH_CHALLENGE_TIMEOUT).blockUntilSuccess()
|
||||
|
||||
if (success) {
|
||||
Log.i(TAG, "Successfully responded to a push challenge. Retrying message send.")
|
||||
return Result.RETRY_NOW
|
||||
} else {
|
||||
Log.w(TAG, "Failed to respond to the push challeng in time. Falling back.")
|
||||
}
|
||||
}
|
||||
} catch (e: NonSuccessfulResponseCodeException) {
|
||||
Log.w(TAG, "[Proof Required] Could not request a push challenge (${e.code}). Falling back.", e)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "[Proof Required] Network error when requesting push challenge. Retrying later.")
|
||||
return Result.RETRY_LATER
|
||||
}
|
||||
|
||||
if (messageId > 0) {
|
||||
Log.w(TAG, "[Proof Required] Marking message as rate-limited. (id: $messageId, thread: $threadId)")
|
||||
SignalDatabase.messages.markAsRateLimited(messageId)
|
||||
}
|
||||
|
||||
if (ProofRequiredException.Option.CAPTCHA in proofRequired.options) {
|
||||
Log.i(TAG, "[Proof Required] CAPTCHA required.")
|
||||
SignalStore.rateLimit.markNeedsRecaptcha(proofRequired.token)
|
||||
|
||||
if (recipient != null && messageId > -1L) {
|
||||
val groupReply: ParentStoryId.GroupReply? = SignalDatabase.messages.getParentStoryIdForGroupReply(messageId)
|
||||
AppDependencies.messageNotifier.notifyProofRequired(context, recipient, ConversationId.fromThreadAndReply(threadId, groupReply))
|
||||
} else {
|
||||
Log.w(TAG, "[Proof Required] No recipient! Couldn't notify.")
|
||||
}
|
||||
}
|
||||
|
||||
return Result.RETHROW
|
||||
}
|
||||
|
||||
enum class Result {
|
||||
/**
|
||||
* The challenge was successful and the message send can be retried immediately.
|
||||
*/
|
||||
RETRY_NOW,
|
||||
|
||||
/**
|
||||
* The challenge failed due to a network error and should be scheduled to retry with some offset.
|
||||
*/
|
||||
RETRY_LATER,
|
||||
|
||||
/**
|
||||
* The caller should rethrow the original error.
|
||||
*/
|
||||
RETHROW;
|
||||
|
||||
fun isRetry() = this != RETHROW
|
||||
}
|
||||
|
||||
private class PushChallengeRequest(val timeout: Duration) {
|
||||
private val latch = CountDownLatch(1)
|
||||
private val eventBus = EventBus.getDefault()
|
||||
|
||||
fun blockUntilSuccess(): Boolean {
|
||||
eventBus.register(this)
|
||||
|
||||
return try {
|
||||
latch.await(timeout.inWholeMilliseconds, TimeUnit.MILLISECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.w(TAG, "[Proof Required] Interrupted?", e)
|
||||
false
|
||||
} finally {
|
||||
eventBus.unregister(this)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
fun onSuccessReceived(event: SuccessEvent) {
|
||||
Log.i(TAG, "[Proof Required] Received a successful result!")
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,8 +10,11 @@ import android.webkit.WebView;
|
|||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.contract.ActivityResultContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
|
@ -20,7 +23,6 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
|
@ -36,10 +38,6 @@ public class RecaptchaProofActivity extends PassphraseRequiredActivity {
|
|||
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
|
||||
public static @NonNull Intent getIntent(@NonNull Context context) {
|
||||
return new Intent(context, RecaptchaProofActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
|
@ -120,6 +118,7 @@ public class RecaptchaProofActivity extends PassphraseRequiredActivity {
|
|||
if (result.clearState) {
|
||||
Log.i(TAG, "Considering the response sufficient to clear the slate.");
|
||||
SignalStore.rateLimit().onProofAccepted();
|
||||
setResult(RESULT_OK);
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
|
@ -140,4 +139,17 @@ public class RecaptchaProofActivity extends PassphraseRequiredActivity {
|
|||
this.success = success;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RecaptchaProofContract extends ActivityResultContract<Void, Boolean> {
|
||||
|
||||
@Override
|
||||
public @NonNull Intent createIntent(@NonNull Context context, Void unused) {
|
||||
return new Intent(context, RecaptchaProofActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
|
||||
return resultCode == RESULT_OK;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package org.thoughtcrime.securesms.ratelimit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
@ -24,6 +26,8 @@ public final class RecaptchaProofBottomSheetFragment extends BottomSheetDialogFr
|
|||
|
||||
private static final String TAG = Log.tag(RecaptchaProofBottomSheetFragment.class);
|
||||
|
||||
private ActivityResultLauncher<Void> launcher;
|
||||
|
||||
public static void show(@NonNull FragmentManager manager) {
|
||||
new RecaptchaProofBottomSheetFragment().show(manager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
|
@ -38,11 +42,25 @@ public final class RecaptchaProofBottomSheetFragment extends BottomSheetDialogFr
|
|||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.recaptcha_required_bottom_sheet, container, false);
|
||||
|
||||
view.findViewById(R.id.recaptcha_sheet_ok_button).setOnClickListener(v -> {
|
||||
Activity activity = requireActivity();
|
||||
final Callback callback;
|
||||
|
||||
if (activity instanceof Callback) {
|
||||
callback = (Callback) activity;
|
||||
} else {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
launcher = registerForActivityResult(new RecaptchaProofActivity.RecaptchaProofContract(), (isOk) -> {
|
||||
if (isOk && callback != null) {
|
||||
callback.onProofCompleted();
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss();
|
||||
startActivity(RecaptchaProofActivity.getIntent(requireContext()));
|
||||
});
|
||||
|
||||
view.findViewById(R.id.recaptcha_sheet_ok_button).setOnClickListener(v -> launcher.launch(null));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -62,4 +80,12 @@ public final class RecaptchaProofBottomSheetFragment extends BottomSheetDialogFr
|
|||
Log.i(TAG, "Ignoring repeat show.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional callback interface to be invoked when the user successfully completes a push challenge.
|
||||
* This is expected to be implemented on the activity which is displaying this fragment.
|
||||
*/
|
||||
public interface Callback {
|
||||
void onProofCompleted();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -331,4 +331,18 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
|||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleResendMediaKeys(@NonNull WebRtcServiceState currentState) {
|
||||
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
|
||||
if (groupCall != null) {
|
||||
try {
|
||||
currentState.getCallInfoState().getGroupCall().resendMediaKeys();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to resend media keys", e);
|
||||
}
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
|
|||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
|
@ -56,6 +55,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendUtil;
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -85,6 +85,7 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess
|
|||
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage;
|
||||
|
||||
|
@ -801,6 +802,11 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
Log.i(TAG, "onSendCallMessage onFailure: ", e);
|
||||
RetrieveProfileJob.enqueue(recipient.getId());
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), UNTRUSTED_IDENTITY));
|
||||
} catch (ProofRequiredException e) {
|
||||
Log.i(TAG, "onSendCallMessage onFailure: ", e);
|
||||
ProofRequiredExceptionHandler.handle(context, e, recipient, -1L, -1L);
|
||||
process((s, p) -> p.handleResendMediaKeys(s));
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), NETWORK_FAILURE));
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "onSendCallMessage onFailure: ", e);
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), NETWORK_FAILURE));
|
||||
|
@ -1147,6 +1153,10 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
isCallFull));
|
||||
}
|
||||
|
||||
public void resendMediaKeys() {
|
||||
process((s, p) -> p.handleResendMediaKeys(s));
|
||||
}
|
||||
|
||||
public void sendCallMessage(@NonNull final RemotePeer remotePeer,
|
||||
@NonNull final SignalServiceCallMessage callMessage)
|
||||
{
|
||||
|
@ -1170,6 +1180,11 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
UNTRUSTED_IDENTITY,
|
||||
Optional.ofNullable(e.getIdentityKey())));
|
||||
} catch (IOException e) {
|
||||
if (e instanceof ProofRequiredException) {
|
||||
ProofRequiredExceptionHandler.handle(context, (ProofRequiredException) e, null, -1L, -1L);
|
||||
process((s, p) -> p.handleResendMediaKeys(s));
|
||||
}
|
||||
|
||||
processSendMessageFailureWithChangeDetection(remotePeer,
|
||||
(s, p) -> p.handleMessageSentError(s,
|
||||
remotePeer.getCallId(),
|
||||
|
|
|
@ -793,6 +793,11 @@ public abstract class WebRtcActionProcessor {
|
|||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleResendMediaKeys(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleResendMediaKeys not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage():");
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue