package org.thoughtcrime.securesms.messagerequests; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import androidx.core.util.Consumer; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.jobs.MultiDeviceMessageRequestResponseJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; import java.util.List; import java.util.concurrent.Executor; final class MessageRequestRepository { private static final String TAG = Log.tag(MessageRequestRepository.class); private final Context context; private final Executor executor; MessageRequestRepository(@NonNull Context context) { this.context = context.getApplicationContext(); this.executor = SignalExecutors.BOUNDED; } void getGroups(@NonNull RecipientId recipientId, @NonNull Consumer> onGroupsLoaded) { executor.execute(() -> { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); onGroupsLoaded.accept(groupDatabase.getPushGroupNamesContainingMember(recipientId)); }); } void getMemberCount(@NonNull RecipientId recipientId, @NonNull Consumer onMemberCountLoaded) { executor.execute(() -> { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); Optional groupRecord = groupDatabase.getGroup(recipientId); onMemberCountLoaded.accept(groupRecord.transform(record -> { if (record.isV2Group()) { DecryptedGroup decryptedGroup = record.requireV2GroupProperties().getDecryptedGroup(); return new GroupMemberCount(decryptedGroup.getMembersCount(), decryptedGroup.getPendingMembersCount()); } else { return new GroupMemberCount(record.getMembers().size(), 0); } }).or(GroupMemberCount.ZERO)); }); } void getMessageRequestState(@NonNull Recipient recipient, long threadId, @NonNull Consumer state) { executor.execute(() -> state.accept(findMessageRequestState(recipient, threadId))); } @WorkerThread private MessageRequestState findMessageRequestState(@NonNull Recipient recipient, long threadId) { if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) { if (recipient.isGroup()) { GroupDatabase.MemberLevel memberLevel = DatabaseFactory.getGroupDatabase(context) .getGroup(recipient.getId()) .transform(g -> g.memberLevel(Recipient.self())) .or(GroupDatabase.MemberLevel.NOT_A_MEMBER); if (memberLevel == GroupDatabase.MemberLevel.NOT_A_MEMBER) { return MessageRequestState.NOT_REQUIRED; } } return MessageRequestState.REQUIRED; } else if (RecipientUtil.isPreMessageRequestThread(context, threadId) && !RecipientUtil.isLegacyProfileSharingAccepted(recipient)) { return MessageRequestState.LEGACY; } else { return MessageRequestState.NOT_REQUIRED; } } void acceptMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestAccepted, @NonNull GroupChangeErrorCallback error) { executor.execute(()-> { if (liveRecipient.get().isPushV2Group()) { try { Log.i(TAG, "GV2 accepting invite"); GroupManager.acceptInvite(context, liveRecipient.get().requireGroupId().requireV2()); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); recipientDatabase.setProfileSharing(liveRecipient.getId(), true); onMessageRequestAccepted.run(); } catch (GroupChangeException | IOException e) { Log.w(TAG, e); error.onError(GroupChangeFailureReason.fromException(e)); } } else { RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); recipientDatabase.setProfileSharing(liveRecipient.getId(), true); MessageSender.sendProfileKey(context, threadId); List messageIds = DatabaseFactory.getThreadDatabase(context) .setEntireThreadRead(threadId); ApplicationDependencies.getMessageNotifier().updateNotification(context); MarkReadReceiver.process(context, messageIds); if (TextSecurePreferences.isMultiDevice(context)) { ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId())); } onMessageRequestAccepted.run(); } }); } void deleteMessageRequest(@NonNull LiveRecipient recipient, long threadId, @NonNull Runnable onMessageRequestDeleted, @NonNull GroupChangeErrorCallback error) { executor.execute(() -> { Recipient resolved = recipient.resolve(); if (resolved.isGroup() && resolved.requireGroupId().isPush()) { try { GroupManager.leaveGroupFromBlockOrMessageRequest(context, resolved.requireGroupId().requirePush()); } catch (GroupChangeException | IOException e) { Log.w(TAG, e); error.onError(GroupChangeFailureReason.fromException(e)); return; } } if (TextSecurePreferences.isMultiDevice(context)) { ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forDelete(recipient.getId())); } ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); threadDatabase.deleteConversation(threadId); onMessageRequestDeleted.run(); }); } void blockMessageRequest(@NonNull LiveRecipient liveRecipient, @NonNull Runnable onMessageRequestBlocked, @NonNull GroupChangeErrorCallback error) { executor.execute(() -> { Recipient recipient = liveRecipient.resolve(); try { RecipientUtil.block(context, recipient); } catch (GroupChangeException | IOException e) { Log.w(TAG, e); error.onError(GroupChangeFailureReason.fromException(e)); return; } liveRecipient.refresh(); if (TextSecurePreferences.isMultiDevice(context)) { ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forBlock(liveRecipient.getId())); } onMessageRequestBlocked.run(); }); } void blockAndDeleteMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestBlocked, @NonNull GroupChangeErrorCallback error) { executor.execute(() -> { Recipient recipient = liveRecipient.resolve(); try{ RecipientUtil.block(context, recipient); } catch (GroupChangeException | IOException e) { Log.w(TAG, e); error.onError(GroupChangeFailureReason.fromException(e)); return; } liveRecipient.refresh(); DatabaseFactory.getThreadDatabase(context).deleteConversation(threadId); if (TextSecurePreferences.isMultiDevice(context)) { ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forBlockAndDelete(liveRecipient.getId())); } onMessageRequestBlocked.run(); }); } void unblockAndAccept(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestUnblocked) { executor.execute(() -> { Recipient recipient = liveRecipient.resolve(); RecipientUtil.unblock(context, recipient); List messageIds = DatabaseFactory.getThreadDatabase(context) .setEntireThreadRead(threadId); ApplicationDependencies.getMessageNotifier().updateNotification(context); MarkReadReceiver.process(context, messageIds); if (TextSecurePreferences.isMultiDevice(context)) { ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId())); } onMessageRequestUnblocked.run(); }); } boolean isPendingMember(@NonNull GroupId.V2 groupId) { return DatabaseFactory.getGroupDatabase(context).isPendingMember(groupId, Recipient.self()); } enum MessageRequestState { /** * Message request permission does not need to be gained at this time. *

* Either: * - Explicit message request has been accepted, or; * - Did not need to be shown because they are a contact etc, or; * - It's a group that they are no longer in or invited to. */ NOT_REQUIRED, /** Explicit message request permission is required. */ REQUIRED, LEGACY } }