use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by WhisperSystems.
the class ConversationParentFragment method updateReminders.
protected void updateReminders() {
Context context = getContext();
if (callback.onUpdateReminders() || context == null) {
return;
}
Optional<Reminder> inviteReminder = inviteReminderModel.getReminder();
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
List<RecipientId> gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue();
if (UnauthorizedReminder.isEligible(context)) {
reminderView.get().showReminder(new UnauthorizedReminder(context));
} else if (ExpiredBuildReminder.isEligible()) {
reminderView.get().showReminder(new ExpiredBuildReminder(context));
reminderView.get().setOnActionClickListener(this::handleReminderAction);
} else if (ServiceOutageReminder.isEligible(context)) {
ApplicationDependencies.getJobManager().add(new ServiceOutageDetectionJob());
reminderView.get().showReminder(new ServiceOutageReminder(context));
} else if (SignalStore.account().isRegistered() && TextSecurePreferences.isShowInviteReminders(context) && !isSecureText && inviteReminder.isPresent() && !recipient.get().isGroup()) {
reminderView.get().setOnActionClickListener(this::handleReminderAction);
reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder());
reminderView.get().showReminder(inviteReminder.get());
} else if (actionableRequestingMembers != null && actionableRequestingMembers > 0) {
reminderView.get().showReminder(PendingGroupJoinRequestsReminder.create(context, actionableRequestingMembers));
reminderView.get().setOnActionClickListener(id -> {
if (id == R.id.reminder_action_review_join_requests) {
startActivity(ManagePendingAndRequestingMembersActivity.newIntent(context, getRecipient().getGroupId().get().requireV2()));
}
});
} else if (gv1MigrationSuggestions != null && gv1MigrationSuggestions.size() > 0 && recipient.get().isPushV2Group()) {
reminderView.get().showReminder(new GroupsV1MigrationSuggestionsReminder(context, gv1MigrationSuggestions));
reminderView.get().setOnActionClickListener(actionId -> {
if (actionId == R.id.reminder_action_gv1_suggestion_add_members) {
GroupsV1MigrationSuggestionsDialog.show(requireActivity(), recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions);
} else if (actionId == R.id.reminder_action_gv1_suggestion_no_thanks) {
groupViewModel.onSuggestedMembersBannerDismissed(recipient.get().requireGroupId(), gv1MigrationSuggestions);
}
});
reminderView.get().setOnDismissListener(() -> {
});
} else if (isInBubble() && !SignalStore.tooltips().hasSeenBubbleOptOutTooltip() && Build.VERSION.SDK_INT > 29) {
reminderView.get().showReminder(new BubbleOptOutReminder(context));
reminderView.get().setOnActionClickListener(actionId -> {
SignalStore.tooltips().markBubbleOptOutTooltipSeen();
reminderView.get().hide();
if (actionId == R.id.reminder_action_turn_off) {
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS).putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().getPackageName()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
} else if (reminderView.resolved()) {
reminderView.get().hide();
}
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method handleCallOfferMessage.
private void handleCallOfferMessage(@NonNull SignalServiceContent content, @NonNull OfferMessage message, @NonNull Optional<Long> smsMessageId, @NonNull Recipient senderRecipient) {
log(String.valueOf(content.getTimestamp()), "handleCallOfferMessage...");
if (smsMessageId.isPresent()) {
MessageDatabase database = SignalDatabase.sms();
database.markAsMissedCall(smsMessageId.get(), message.getType() == OfferMessage.Type.VIDEO_CALL);
} else {
RemotePeer remotePeer = new RemotePeer(senderRecipient.getId(), new CallId(message.getId()));
byte[] remoteIdentityKey = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
ApplicationDependencies.getSignalCallManager().receivedOffer(new WebRtcData.CallMetadata(remotePeer, content.getSenderDevice()), new WebRtcData.OfferMetadata(message.getOpaque(), message.getSdp(), message.getType()), new WebRtcData.ReceivedOfferMetadata(remoteIdentityKey, content.getServerReceivedTimestamp(), content.getServerDeliveredTimestamp(), content.getCallMessage().get().isMultiRing()));
}
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method getValidatedQuote.
private Optional<QuoteModel> getValidatedQuote(Optional<SignalServiceDataMessage.Quote> quote) {
if (!quote.isPresent())
return Optional.absent();
if (quote.get().getId() <= 0) {
warn("Received quote without an ID! Ignoring...");
return Optional.absent();
}
if (quote.get().getAuthor() == null) {
warn("Received quote without an author! Ignoring...");
return Optional.absent();
}
RecipientId author = Recipient.externalPush(quote.get().getAuthor()).getId();
MessageRecord message = SignalDatabase.mmsSms().getMessageFor(quote.get().getId(), author);
if (message != null && !message.isRemoteDelete()) {
log("Found matching message record...");
List<Attachment> attachments = new LinkedList<>();
List<Mention> mentions = new LinkedList<>();
if (message.isMms()) {
MmsMessageRecord mmsMessage = (MmsMessageRecord) message;
mentions.addAll(SignalDatabase.mentions().getMentionsForMessage(mmsMessage.getId()));
if (mmsMessage.isViewOnce()) {
attachments.add(new TombstoneAttachment(MediaUtil.VIEW_ONCE, true));
} else {
attachments = mmsMessage.getSlideDeck().asAttachments();
if (attachments.isEmpty()) {
attachments.addAll(Stream.of(mmsMessage.getLinkPreviews()).filter(lp -> lp.getThumbnail().isPresent()).map(lp -> lp.getThumbnail().get()).toList());
}
}
}
return Optional.of(new QuoteModel(quote.get().getId(), author, message.getBody(), false, attachments, mentions));
} else if (message != null) {
warn("Found the target for the quote, but it's flagged as remotely deleted.");
}
warn("Didn't find matching message record...");
return Optional.of(new QuoteModel(quote.get().getId(), author, quote.get().getText(), true, PointerAttachment.forPointers(quote.get().getAttachments()), getMentions(quote.get().getMentions())));
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by WhisperSystems.
the class GroupSendUtil method sendMessage.
/**
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
* {@link SendMessageResult}s just like we're used to.
*
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
*/
@WorkerThread
private static List<SendMessageResult> sendMessage(@NonNull Context context, @Nullable GroupId.V2 groupId, @Nullable MessageId relatedMessageId, @NonNull List<Recipient> allTargets, boolean isRecipientUpdate, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException {
Log.i(TAG, "Starting group send. GroupId: " + (groupId != null ? groupId.toString() : "none") + ", RelatedMessageId: " + (relatedMessageId != null ? relatedMessageId.toString() : "none") + ", Targets: " + allTargets.size() + ", RecipientUpdate: " + isRecipientUpdate + ", Operation: " + sendOperation.getClass().getSimpleName());
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
RecipientData recipients = new RecipientData(context, registeredTargets);
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.absent();
List<Recipient> senderKeyTargets = new LinkedList<>();
List<Recipient> legacyTargets = new LinkedList<>();
for (Recipient recipient : registeredTargets) {
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
boolean validMembership = groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId());
if (recipient.getSenderKeyCapability() == Recipient.Capability.SUPPORTED && recipient.hasServiceId() && access.isPresent() && access.get().getTargetUnidentifiedAccess().isPresent() && validMembership) {
senderKeyTargets.add(recipient);
} else {
legacyTargets.add(recipient);
}
}
if (groupId == null) {
Log.i(TAG, "Recipients not in a group. Using legacy.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
Log.i(TAG, "All of our devices do not support sender key. Using legacy.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else if (SignalStore.internalValues().removeSenderKeyMinimum()) {
Log.i(TAG, "Sender key minimum removed. Using for " + senderKeyTargets.size() + " recipients.");
} else if (senderKeyTargets.size() < 2) {
Log.i(TAG, "Too few sender-key-capable users (" + senderKeyTargets.size() + "). Doing all legacy sends.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else {
Log.i(TAG, "Can use sender key for " + senderKeyTargets.size() + "/" + allTargets.size() + " recipients.");
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyStarted(relatedMessageId.getId());
}
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
if (senderKeyTargets.size() > 0 && groupId != null) {
DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
long keyAge = System.currentTimeMillis() - keyCreateTime;
if (keyCreateTime != -1 && keyAge > FeatureFlags.senderKeyMaxAge()) {
Log.w(TAG, "DistributionId " + distributionId + " was created at " + keyCreateTime + " and is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
SenderKeyUtil.rotateOurKey(context, distributionId);
}
try {
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate);
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
if (sendOperation.shouldIncludeInMessageLog()) {
SignalDatabase.messageLog().insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
} catch (InvalidUnidentifiedAccessHeaderException e) {
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NoSessionException e) {
Log.w(TAG, "No session. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidKeyException e) {
Log.w(TAG, "Invalid key. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidRegistrationIdException e) {
Log.w(TAG, "Invalid registrationId. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NotFoundException e) {
Log.w(TAG, "Someone was unregistered. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
}
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyShared(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyEncrypted(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeySyncSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
boolean onlyTargetIsSelfWithLinkedDevice = legacyTargets.isEmpty() && senderKeyTargets.isEmpty() && TextSecurePreferences.isMultiDevice(context);
if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) {
if (legacyTargets.size() > 0) {
Log.i(TAG, "Need to do " + legacyTargets.size() + " legacy sends.");
} else {
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
}
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
final MessageSendLogDatabase messageLogDatabase = SignalDatabase.messageLog();
final AtomicLong entryId = new AtomicLong(-1);
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, result -> {
if (!includeInMessageLog) {
return;
}
synchronized (entryId) {
if (entryId.get() == -1) {
entryId.set(messageLogDatabase.insertIfPossible(recipients.requireRecipientId(result.getAddress()), sendOperation.getSentTimestamp(), result, sendOperation.getContentHint(), sendOperation.getRelatedMessageId()));
} else {
messageLogDatabase.addRecipientToExistingEntryIfPossible(entryId.get(), recipients.requireRecipientId(result.getAddress()), result);
}
}
}, cancelationSignal);
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
}
if (unregisteredTargets.size() > 0) {
Log.w(TAG, "There are " + unregisteredTargets.size() + " unregistered targets. Including failure results.");
List<SendMessageResult> unregisteredResults = unregisteredTargets.stream().filter(Recipient::hasServiceId).map(t -> SendMessageResult.unregisteredFailure(new SignalServiceAddress(t.requireServiceId(), t.getE164().orNull()))).collect(Collectors.toList());
if (unregisteredResults.size() < unregisteredTargets.size()) {
Log.w(TAG, "There are " + (unregisteredTargets.size() - unregisteredResults.size()) + " targets that have no UUID! Cannot report a failure for them.");
}
allResults.addAll(unregisteredResults);
}
return allResults;
}
use of org.whispersystems.libsignal.util.guava.Optional in project Signal-Android by WhisperSystems.
the class IdentityUtil method getRemoteIdentityKey.
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
final RecipientId recipientId = recipient.getId();
SimpleTask.run(SignalExecutors.BOUNDED, () -> ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipientId), future::set);
return future;
}
Aggregations