From 0e2d52026e1d1276d0e5b95abb5c77b3f37c4b2f Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 7 Aug 2019 14:22:51 -0400 Subject: [PATCH] Migrated to locally-assigned RecipientId's. Oh boy. --- .../securesms/ApplicationContext.java | 4 +- .../securesms/BindableConversationItem.java | 3 +- .../securesms/BlockedContactsActivity.java | 16 +- .../securesms/ConfirmIdentityDialog.java | 26 +- .../securesms/ConversationListActivity.java | 9 +- .../securesms/ConversationListAdapter.java | 2 +- .../ConversationListArchiveActivity.java | 2 +- .../securesms/ConversationListItem.java | 62 +- .../securesms/GroupCreateActivity.java | 24 +- .../securesms/GroupMembersDialog.java | 6 +- .../securesms/IncomingMessageProcessor.java | 10 +- .../securesms/InviteActivity.java | 4 +- .../securesms/MediaOverviewActivity.java | 46 +- .../securesms/MediaPreviewActivity.java | 29 +- .../securesms/MessageDetailsActivity.java | 31 +- .../MessageDetailsRecipientAdapter.java | 2 +- .../securesms/MessageRecipientListItem.java | 25 +- .../securesms/NewConversationActivity.java | 4 +- .../RecipientPreferenceActivity.java | 148 ++-- .../securesms/RegistrationActivity.java | 3 +- .../thoughtcrime/securesms/ShareActivity.java | 49 +- .../securesms/ShortcutLauncherActivity.java | 17 +- .../securesms/SmsSendtoActivity.java | 4 +- .../securesms/VerifyIdentityActivity.java | 71 +- .../securesms/WebRtcCallActivity.java | 6 +- .../securesms/backup/FullBackupImporter.java | 3 +- .../securesms/components/AvatarImageView.java | 2 +- .../securesms/components/InputPanel.java | 2 +- .../components/PushRecipientsPanel.java | 26 +- .../securesms/components/QuoteView.java | 53 +- .../components/SharedContactView.java | 37 +- .../components/TypingStatusRepository.java | 4 +- .../identity/UntrustedSendDialog.java | 2 +- .../identity/UnverifiedSendDialog.java | 2 +- .../components/reminder/InviteReminder.java | 17 +- .../components/webrtc/WebRtcCallScreen.java | 40 +- .../securesms/contacts/ContactAccessor.java | 8 +- .../contacts/ContactSelectionListItem.java | 35 +- .../contacts/ContactsCursorLoader.java | 7 +- .../securesms/contacts/ContactsDatabase.java | 5 +- .../securesms/contacts/RecipientsEditor.java | 4 +- .../contactshare/ContactRepository.java | 7 +- .../securesms/contactshare/ContactUtil.java | 13 +- .../SharedContactDetailsActivity.java | 37 +- .../conversation/ConversationActivity.java | 268 +++---- .../conversation/ConversationFragment.java | 31 +- .../conversation/ConversationItem.java | 73 +- .../ConversationPopupActivity.java | 2 +- .../conversation/ConversationTitleView.java | 6 +- .../conversation/ConversationUpdateItem.java | 42 +- .../storage/TextSecureIdentityKeyStore.java | 25 +- .../storage/TextSecureSessionStore.java | 27 +- .../securesms/database/Address.java | 228 +----- .../securesms/database/EarlyReceiptCache.java | 21 +- .../securesms/database/GroupDatabase.java | 120 +-- .../database/GroupReceiptDatabase.java | 41 +- .../securesms/database/IdentityDatabase.java | 45 +- .../securesms/database/MediaDatabase.java | 28 +- .../securesms/database/MessagingDatabase.java | 46 +- .../securesms/database/MmsDatabase.java | 130 ++-- .../securesms/database/MmsSmsColumns.java | 2 +- .../securesms/database/MmsSmsDatabase.java | 44 +- .../securesms/database/RecipientDatabase.java | 450 ++++++----- .../securesms/database/SearchDatabase.java | 28 +- .../securesms/database/SessionDatabase.java | 63 +- .../securesms/database/SmsDatabase.java | 100 +-- .../securesms/database/SmsMigrator.java | 67 +- .../securesms/database/ThreadDatabase.java | 82 +- .../documents/IdentityKeyMismatch.java | 42 +- .../database/documents/NetworkFailure.java | 40 +- .../helpers/RecipientIdMigrationHelper.java | 282 +++++++ .../database/helpers/SQLCipherOpenHelper.java | 28 +- .../helpers/SessionStoreMigrationHelper.java | 2 +- .../database/identity/IdentityRecordList.java | 4 +- .../loaders/BlockedContactsLoader.java | 3 +- .../loaders/BucketedThreadMediaLoader.java | 11 +- .../loaders/ConversationListLoader.java | 13 +- .../database/loaders/ThreadMediaLoader.java | 17 +- .../securesms/database/model/Quote.java | 16 +- .../database/model/ThreadRecord.java | 3 +- .../dependencies/ApplicationDependencies.java | 15 +- .../ApplicationDependencyProvider.java | 7 + .../securesms/events/WebRtcViewModel.java | 2 +- .../securesms/groups/GroupManager.java | 11 +- .../groups/GroupMessageProcessor.java | 44 +- .../securesms/groups/V1GroupManager.java | 68 +- .../securesms/jobmanager/JobMigration.java | 8 +- .../migrations/RecipientIdJobMigration.java | 226 ++++++ .../securesms/jobs/DirectoryRefreshJob.java | 20 +- .../securesms/jobs/JobManagerFactories.java | 14 +- .../securesms/jobs/MmsDownloadJob.java | 46 +- .../securesms/jobs/MmsReceiveJob.java | 2 +- .../securesms/jobs/MmsSendJob.java | 8 +- .../jobs/MultiDeviceBlockedUpdateJob.java | 6 +- .../jobs/MultiDeviceContactUpdateJob.java | 58 +- .../jobs/MultiDeviceGroupUpdateJob.java | 9 +- .../jobs/MultiDeviceReadUpdateJob.java | 19 +- .../jobs/MultiDeviceVerifiedUpdateJob.java | 18 +- .../jobs/MultiDeviceViewOnceOpenJob.java | 18 +- .../securesms/jobs/PushDecryptJob.java | 133 ++-- .../securesms/jobs/PushGroupSendJob.java | 73 +- .../securesms/jobs/PushGroupUpdateJob.java | 29 +- .../securesms/jobs/PushMediaSendJob.java | 22 +- .../securesms/jobs/PushSendJob.java | 9 +- .../securesms/jobs/PushTextSendJob.java | 16 +- .../securesms/jobs/RequestGroupInfoJob.java | 19 +- .../jobs/RetrieveProfileAvatarJob.java | 31 +- .../securesms/jobs/RetrieveProfileJob.java | 37 +- .../securesms/jobs/RotateProfileKeyJob.java | 4 +- .../jobs/SendDeliveryReceiptJob.java | 32 +- .../securesms/jobs/SendReadReceiptJob.java | 50 +- .../securesms/jobs/SmsReceiveJob.java | 6 +- .../securesms/jobs/SmsSendJob.java | 12 +- .../securesms/jobs/SmsSentJob.java | 2 +- .../securesms/jobs/TypingSendJob.java | 8 +- .../longmessage/LongMessageActivity.java | 29 +- .../mediasend/CameraContactAdapter.java | 2 +- .../CameraContactSelectionAdapter.java | 2 +- .../mediasend/CameraContactsRepository.java | 14 +- .../mediasend/MediaSendActivity.java | 50 +- .../migrations/ApplicationMigrations.java | 7 +- .../securesms/mms/IncomingMediaMessage.java | 37 +- .../thoughtcrime/securesms/mms/QuoteId.java | 26 +- .../securesms/mms/QuoteModel.java | 8 +- .../AndroidAutoReplyReceiver.java | 7 +- .../notifications/MarkReadReceiver.java | 13 +- .../notifications/MessageNotifier.java | 2 +- .../notifications/NotificationChannels.java | 26 +- .../notifications/NotificationItem.java | 2 +- .../notifications/NotificationState.java | 6 +- .../notifications/RemoteReplyReceiver.java | 20 +- .../securesms/notifications/ReplyMethod.java | 2 +- .../SingleRecipientNotificationBuilder.java | 4 +- .../{util => phonenumbers}/NumberUtil.java | 24 +- .../phonenumbers/PhoneNumberFormatter.java | 200 +++++ .../preferences/BlockedContactListItem.java | 36 +- .../profiles/GroupShareProfileView.java | 2 +- .../securesms/profiles/UnknownSenderView.java | 4 +- .../securesms/recipients/LiveRecipient.java | 193 +++++ .../recipients/LiveRecipientCache.java | 62 ++ .../securesms/recipients/Recipient.java | 729 +++++------------- .../recipients/RecipientDetails.java | 87 +++ .../recipients/RecipientExporter.java | 2 +- .../recipients/RecipientForeverObserver.java | 9 + .../securesms/recipients/RecipientId.java | 115 +++ .../recipients/RecipientModifiedListener.java | 4 +- .../recipients/RecipientProvider.java | 235 ------ .../securesms/search/SearchFragment.java | 2 +- .../securesms/search/SearchRepository.java | 24 +- .../securesms/service/DirectShareService.java | 9 +- .../service/QuickResponseService.java | 3 +- .../securesms/service/WebRtcCallService.java | 26 +- .../securesms/sms/IncomingJoinedMessage.java | 7 +- .../securesms/sms/IncomingTextMessage.java | 37 +- .../securesms/sms/MessageSender.java | 36 +- .../securesms/util/AttachmentUtil.java | 2 +- .../securesms/util/CommunicationActions.java | 4 +- .../securesms/util/DelimiterUtil.java | 11 +- .../securesms/util/DirectoryHelper.java | 93 +-- .../securesms/util/GroupUtil.java | 19 +- .../securesms/util/IdentityUtil.java | 28 +- .../util/SelectedRecipientsAdapter.java | 2 +- src/org/thoughtcrime/securesms/util/Util.java | 14 +- .../securesms/util/VerifySpan.java | 12 +- .../securesms/webrtc/VoiceCallShare.java | 5 +- .../RecipientIdJobMigrationTest.java | 300 +++++++ .../PhoneNumberFormatterTest.java} | 22 +- .../recipients/RecipientExporterTest.java | 3 +- 168 files changed, 3927 insertions(+), 3110 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java create mode 100644 src/org/thoughtcrime/securesms/jobmanager/migrations/RecipientIdJobMigration.java rename src/org/thoughtcrime/securesms/{util => phonenumbers}/NumberUtil.java (63%) create mode 100644 src/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java create mode 100644 src/org/thoughtcrime/securesms/recipients/LiveRecipient.java create mode 100644 src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java create mode 100644 src/org/thoughtcrime/securesms/recipients/RecipientDetails.java create mode 100644 src/org/thoughtcrime/securesms/recipients/RecipientForeverObserver.java create mode 100644 src/org/thoughtcrime/securesms/recipients/RecipientId.java delete mode 100644 src/org/thoughtcrime/securesms/recipients/RecipientProvider.java create mode 100644 test/unitTest/java/org/thoughtcrime/securesms/jobmanager/migrations/RecipientIdJobMigrationTest.java rename test/unitTest/java/org/thoughtcrime/securesms/{database/AddressTest.java => phonenumbers/PhoneNumberFormatterTest.java} (75%) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 5b9122ada0..206e34e6dc 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -223,7 +223,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi .setConstraintFactories(JobManagerFactories.getConstraintFactories(this)) .setConstraintObservers(JobManagerFactories.getConstraintObservers(this)) .setJobStorage(new FastJobStorage(DatabaseFactory.getJobDatabase(this))) - .setJobMigrator(new JobMigrator(TextSecurePreferences.getJobManagerVersion(this), 1, JobManagerFactories.getJobMigrations())) + .setJobMigrator(new JobMigrator(TextSecurePreferences.getJobManagerVersion(this), 2, JobManagerFactories.getJobMigrations(this))) .build()); } @@ -338,7 +338,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi private void executePendingContactSync() { if (TextSecurePreferences.needsFullContactSync(this)) { - ApplicationContext.getInstance(this).getJobManager().add(new MultiDeviceContactUpdateJob(this, true)); + ApplicationContext.getInstance(this).getJobManager().add(new MultiDeviceContactUpdateJob(true)); } } diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java index 88d5657177..277e4f74bb 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.stickers.StickerLocator; import org.whispersystems.libsignal.util.guava.Optional; @@ -36,7 +37,7 @@ public interface BindableConversationItem extends Unbindable { interface EventListener { void onQuoteClicked(MmsMessageRecord messageRecord); void onLinkPreviewClicked(@NonNull LinkPreview linkPreview); - void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms); + void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms); void onStickerClicked(@NonNull StickerLocator stickerLocator); void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord); void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView); diff --git a/src/org/thoughtcrime/securesms/BlockedContactsActivity.java b/src/org/thoughtcrime/securesms/BlockedContactsActivity.java index 2392d685f7..816b439d74 100644 --- a/src/org/thoughtcrime/securesms/BlockedContactsActivity.java +++ b/src/org/thoughtcrime/securesms/BlockedContactsActivity.java @@ -17,12 +17,14 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.preferences.BlockedContactListItem; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -78,6 +80,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity getLoaderManager().initLoader(0, null, this); } + @Override + public void onStart() { + super.onStart(); + getLoaderManager().restartLoader(0, null, this); + } + @Override public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); @@ -107,7 +115,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity public void onItemClick(AdapterView parent, View view, int position, long id) { Recipient recipient = ((BlockedContactListItem)view).getRecipient(); Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class); - intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId()); startActivity(intent); } @@ -129,8 +137,8 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity @Override public void bindView(View view, Context context, Cursor cursor) { - String address = cursor.getString(1); - Recipient recipient = Recipient.from(context, Address.fromSerialized(address), true); + RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID))); + LiveRecipient recipient = Recipient.live(recipientId); ((BlockedContactListItem) view).set(glideRequests, recipient); } diff --git a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java index 8652d27d6c..03a229a5e6 100644 --- a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java @@ -12,7 +12,6 @@ import android.text.method.LinkMovementMethod; import android.widget.TextView; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -22,6 +21,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.VerifySpan; @@ -46,7 +46,7 @@ public class ConfirmIdentityDialog extends AlertDialog { { super(context); - Recipient recipient = Recipient.from(context, mismatch.getAddress(), false); + Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context)); String name = recipient.toShortString(); String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name); SpannableString spannableString = new SpannableString(introduction + " " + @@ -59,7 +59,7 @@ public class ConfirmIdentityDialog extends AlertDialog { setTitle(name); setMessage(spannableString); - setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getAddress())); + setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getId())); setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener()); } @@ -78,12 +78,12 @@ public class ConfirmIdentityDialog extends AlertDialog { private final MessageRecord messageRecord; private final IdentityKeyMismatch mismatch; - private final Address address; + private final RecipientId recipientId; - private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) { + private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, RecipientId recipientId) { this.messageRecord = messageRecord; this.mismatch = mismatch; - this.address = address; + this.recipientId = recipientId; } @SuppressLint("StaticFieldLeak") @@ -94,7 +94,7 @@ public class ConfirmIdentityDialog extends AlertDialog { @Override protected Void doInBackground(Void... params) { synchronized (SESSION_LOCK) { - SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(address.toPhoneString(), 1); + SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireAddress().toPhoneString(), 1); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext()); identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true); @@ -137,17 +137,17 @@ public class ConfirmIdentityDialog extends AlertDialog { if (messageRecord.isMms()) { mmsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getAddress(), + mismatch.getRecipientId(getContext()), mismatch.getIdentityKey()); - if (messageRecord.getRecipient().isPushGroupRecipient()) { - MessageSender.resendGroupMessage(getContext(), messageRecord, mismatch.getAddress()); + if (messageRecord.getRecipient().isPushGroup()) { + MessageSender.resendGroupMessage(getContext(), messageRecord, Recipient.resolved(mismatch.getRecipientId(getContext())).getId()); } else { MessageSender.resend(getContext(), messageRecord); } } else { smsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getAddress(), + mismatch.getRecipientId(getContext()), mismatch.getIdentityKey()); MessageSender.resend(getContext(), messageRecord); @@ -160,13 +160,13 @@ public class ConfirmIdentityDialog extends AlertDialog { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext()); smsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getAddress(), + mismatch.getRecipientId(getContext()), mismatch.getIdentityKey()); boolean legacy = !messageRecord.isContentBundleKeyExchange(); SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, - messageRecord.getIndividualRecipient().getAddress().toPhoneString(), + messageRecord.getIndividualRecipient().requireAddress().toPhoneString(), messageRecord.getRecipientDeviceId(), messageRecord.getDateSent(), legacy ? Base64.decode(messageRecord.getBody()) : null, diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index c4ed92b3e9..9e31e25a12 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -45,7 +45,6 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.conversation.ConversationActivity; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.lock.RegistrationLockDialog; @@ -112,9 +111,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit dynamicTheme.onResume(this); dynamicLanguage.onResume(this); - SimpleTask.run(getLifecycle(), () -> { - return Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), false); - }, this::initializeProfileIcon); + SimpleTask.run(getLifecycle(), Recipient::self, this::initializeProfileIcon); } @Override @@ -191,7 +188,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit Drawable fallback = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(this, fallbackColor.toAvatarColor(this)); GlideApp.with(this) - .load(new ProfileContactPhoto(recipient.getAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))) + .load(new ProfileContactPhoto(recipient.requireAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))) .error(fallback) .circleCrop() .diskCacheStrategy(DiskCacheStrategy.ALL) @@ -225,7 +222,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit searchToolbar.clearFocus(); Intent intent = new Intent(this, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()); diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index 4539030378..63746d69b4 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -76,7 +76,7 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter selectedThreads; - private Recipient recipient; + private LiveRecipient recipient; private long threadId; private GlideRequests glideRequests; private View subjectContainer; @@ -132,21 +132,23 @@ public class ConversationListItem extends RelativeLayout boolean batchMode, @Nullable String highlightSubstring) { + if (this.recipient != null) this.recipient.removeForeverObserver(this); + this.selectedThreads = selectedThreads; - this.recipient = thread.getRecipient(); + this.recipient = thread.getRecipient().live(); this.threadId = thread.getThreadId(); this.glideRequests = glideRequests; this.unreadCount = thread.getUnreadCount(); this.distributionType = thread.getDistributionType(); this.lastSeen = thread.getLastSeen(); - this.recipient.addListener(this); + this.recipient.observeForever(this); if (highlightSubstring != null) { - String name = recipient.isLocalNumber() ? getContext().getString(R.string.note_to_self) : recipient.getName(); + String name = recipient.get().isLocalNumber() ? getContext().getString(R.string.note_to_self) : recipient.get().getName(); this.fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), name, highlightSubstring)); } else { - this.fromView.setText(recipient, unreadCount == 0); + this.fromView.setText(recipient.get(), unreadCount == 0); } if (typingThreads.contains(threadId)) { @@ -182,9 +184,9 @@ public class ConversationListItem extends RelativeLayout setStatusIcons(thread); setThumbnailSnippet(thread); setBatchState(batchMode); - setRippleColor(recipient); + setRippleColor(recipient.get()); setUnreadIndicator(thread); - this.contactPhotoImage.setAvatar(glideRequests, recipient, true); + this.contactPhotoImage.setAvatar(glideRequests, recipient.get(), true); } public void bind(@NonNull Recipient contact, @@ -192,16 +194,18 @@ public class ConversationListItem extends RelativeLayout @NonNull Locale locale, @Nullable String highlightSubstring) { + if (this.recipient != null) this.recipient.removeForeverObserver(this); + this.selectedThreads = Collections.emptySet(); - this.recipient = contact; + this.recipient = contact.live(); this.glideRequests = glideRequests; - this.recipient.addListener(this); + this.recipient.observeForever(this); - String name = recipient.isLocalNumber() ? getContext().getString(R.string.note_to_self) : recipient.getName(); + String name = recipient.get().isLocalNumber() ? getContext().getString(R.string.note_to_self) : recipient.get().getName(); fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), name, highlightSubstring)); - subjectView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), contact.getAddress().toString(), highlightSubstring)); + subjectView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), contact.requireAddress().toString(), highlightSubstring)); dateView.setText(""); archivedView.setVisibility(GONE); unreadIndicator.setVisibility(GONE); @@ -211,7 +215,7 @@ public class ConversationListItem extends RelativeLayout setBatchState(false); setRippleColor(contact); - contactPhotoImage.setAvatar(glideRequests, recipient, true); + contactPhotoImage.setAvatar(glideRequests, recipient.get(), true); } public void bind(@NonNull MessageResult messageResult, @@ -219,13 +223,15 @@ public class ConversationListItem extends RelativeLayout @NonNull Locale locale, @Nullable String highlightSubstring) { + if (this.recipient != null) this.recipient.removeForeverObserver(this); + this.selectedThreads = Collections.emptySet(); - this.recipient = messageResult.conversationRecipient; + this.recipient = messageResult.conversationRecipient.live(); this.glideRequests = glideRequests; - this.recipient.addListener(this); + this.recipient.observeForever(this); - fromView.setText(recipient, true); + fromView.setText(recipient.get(), true); subjectView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), messageResult.bodySnippet, highlightSubstring)); dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.receivedTimestampMs)); archivedView.setVisibility(GONE); @@ -235,14 +241,14 @@ public class ConversationListItem extends RelativeLayout thumbnailView.setVisibility(GONE); setBatchState(false); - setRippleColor(recipient); - contactPhotoImage.setAvatar(glideRequests, recipient, true); + setRippleColor(recipient.get()); + contactPhotoImage.setAvatar(glideRequests, recipient.get(), true); } @Override public void unbind() { if (this.recipient != null) { - this.recipient.removeListener(this); + this.recipient.removeForeverObserver(this); this.recipient = null; contactPhotoImage.setAvatar(glideRequests, null, true); } @@ -253,7 +259,7 @@ public class ConversationListItem extends RelativeLayout } public Recipient getRecipient() { - return recipient; + return recipient.get(); } public long getThreadId() { @@ -339,14 +345,10 @@ public class ConversationListItem extends RelativeLayout } @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> { - if (this.recipient == recipient) { - fromView.setText(recipient, unreadCount == 0); - contactPhotoImage.setAvatar(glideRequests, recipient, true); - setRippleColor(recipient); - } - }); + public void onRecipientChanged(@NonNull Recipient recipient) { + fromView.setText(recipient, unreadCount == 0); + contactPhotoImage.setAvatar(glideRequests, recipient, true); + setRippleColor(recipient); } private static class ThumbnailPositioner implements Runnable { diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 518288bded..24b26815a5 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -260,7 +261,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); startActivity(intent); finish(); } @@ -286,9 +287,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity List selected = data.getStringArrayListExtra("contacts"); for (String contact : selected) { - Address address = Address.fromExternal(this, contact); - Recipient recipient = Recipient.from(this, address, false); - + Recipient recipient = Recipient.external(this, contact); addSelectedContacts(recipient); } break; @@ -338,16 +337,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity @Override protected GroupActionResult doInBackground(Void... avoid) { - List
memberAddresses = new LinkedList<>(); + List memberAddresses = new LinkedList<>(); for (Recipient recipient : members) { - memberAddresses.add(recipient.getAddress()); + memberAddresses.add(recipient.getId()); } - memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(activity))); + memberAddresses.add(Recipient.self().getId()); - String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true); - Recipient groupRecipient = Recipient.from(activity, Address.fromSerialized(groupId), true); - long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT); + String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId); + Recipient groupRecipient = Recipient.resolved(groupRecipientId); + long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT); return new GroupActionResult(groupRecipient, threadId); } @@ -450,7 +450,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity if (!activity.isFinishing()) { Intent intent = activity.getIntent(); intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId()); - intent.putExtra(GROUP_ADDRESS_EXTRA, result.get().getGroupRecipient().getAddress()); + intent.putExtra(GROUP_ADDRESS_EXTRA, result.get().getGroupRecipient().requireAddress()); activity.setResult(RESULT_OK, intent); activity.finish(); } @@ -493,7 +493,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity if (failIfNotPush && !isPush) { results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, recipient.toShortString()))); - } else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getAddress().serialize())) { + } else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.requireAddress().serialize())) { results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_youre_already_in_the_group))); } else { results.add(new Result(recipient, isPush, null)); diff --git a/src/org/thoughtcrime/securesms/GroupMembersDialog.java b/src/org/thoughtcrime/securesms/GroupMembersDialog.java index b97840f635..3924ee9d4f 100644 --- a/src/org/thoughtcrime/securesms/GroupMembersDialog.java +++ b/src/org/thoughtcrime/securesms/GroupMembersDialog.java @@ -32,7 +32,7 @@ public class GroupMembersDialog extends AsyncTask> { @Override protected List doInBackground(Void... params) { - return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), true); + return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireAddress().toGroupString(), true); } @Override @@ -66,7 +66,7 @@ public class GroupMembersDialog extends AsyncTask> { if (recipient.getContactUri() != null) { Intent intent = new Intent(context, RecipientPreferenceActivity.class); - intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId()); context.startActivity(intent); } else { @@ -123,7 +123,7 @@ public class GroupMembersDialog extends AsyncTask> { } private boolean isLocalNumber(Recipient recipient) { - return Util.isOwnNumber(context, recipient.getAddress()); + return Util.isOwnNumber(context, recipient.requireAddress()); } } } diff --git a/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java b/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java index c088e36b62..24c0459841 100644 --- a/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java @@ -75,11 +75,10 @@ public class IncomingMessageProcessor { public void processEnvelope(@NonNull SignalServiceEnvelope envelope) { if (envelope.hasSource()) { - Address source = Address.fromExternal(context, envelope.getSource()); - Recipient recipient = Recipient.from(context, source, false); + Recipient recipient = Recipient.external(context, envelope.getSource()); if (!isActiveNumber(recipient)) { - recipientDatabase.setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); + recipientDatabase.setRegistered(recipient.getId(), RecipientDatabase.RegisteredState.REGISTERED); jobManager.add(new DirectoryRefreshJob(recipient, false)); } } @@ -101,9 +100,8 @@ public class IncomingMessageProcessor { private void processReceipt(@NonNull SignalServiceEnvelope envelope) { Log.i(TAG, String.format(Locale.ENGLISH, "Received receipt: (XXXXX, %d)", envelope.getTimestamp())); - mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), - envelope.getTimestamp()), - System.currentTimeMillis()); + mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(Recipient.external(context, envelope.getSource()).getId(), envelope.getTimestamp()), + System.currentTimeMillis()); } private boolean isActiveNumber(@NonNull Recipient recipient) { diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java index ad2a476c3f..8fc8f64d2d 100644 --- a/src/org/thoughtcrime/securesms/InviteActivity.java +++ b/src/org/thoughtcrime/securesms/InviteActivity.java @@ -217,13 +217,13 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen if (context == null) return null; for (String number : numbers) { - Recipient recipient = Recipient.from(context, Address.fromExternal(context, number), false); + Recipient recipient = Recipient.external(context, number); int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null); if (recipient.getContactUri() != null) { - DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient.getId(), true); } } diff --git a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java index d0d77bbea2..c467e0768d 100644 --- a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -51,7 +51,6 @@ import android.widget.Toast; import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader; @@ -59,14 +58,15 @@ import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.Buc import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; @@ -83,15 +83,15 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { @SuppressWarnings("unused") private final static String TAG = MediaOverviewActivity.class.getSimpleName(); - public static final String ADDRESS_EXTRA = "address"; + public static final String RECIPIENT_EXTRA = "recipient_id"; private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private Toolbar toolbar; - private TabLayout tabLayout; - private ViewPager viewPager; - private Recipient recipient; + private Toolbar toolbar; + private TabLayout tabLayout; + private ViewPager viewPager; + private LiveRecipient recipient; @Override protected void onPreCreate() { @@ -129,21 +129,19 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { } private void initializeResources() { - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); + RecipientId recipientId = getIntent().getParcelableExtra(RECIPIENT_EXTRA); this.viewPager = ViewUtil.findById(this, R.id.pager); this.toolbar = ViewUtil.findById(this, R.id.toolbar); this.tabLayout = ViewUtil.findById(this, R.id.tab_layout); - this.recipient = Recipient.from(this, address, true); + this.recipient = Recipient.live(recipientId); } private void initializeToolbar() { setSupportActionBar(this.toolbar); - getSupportActionBar().setTitle(recipient.toShortString()); + getSupportActionBar().setTitle(recipient.get().toShortString()); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - this.recipient.addListener(recipient -> { - Util.runOnMain(() -> getSupportActionBar().setTitle(recipient.toShortString())); - }); + this.recipient.observe(this, recipient -> getSupportActionBar().setTitle(recipient.toShortString())); } public void onEnterMultiSelect() { @@ -171,7 +169,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { else throw new AssertionError(); Bundle args = new Bundle(); - args.putString(MediaOverviewGalleryFragment.ADDRESS_EXTRA, recipient.getAddress().serialize()); + args.putParcelable(MediaOverviewGalleryFragment.RECIPIENT_EXTRA, recipient.getId()); args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, dynamicLanguage.getCurrentLocale()); fragment.setArguments(args); @@ -194,8 +192,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { public static abstract class MediaOverviewFragment extends Fragment implements LoaderManager.LoaderCallbacks { - public static final String ADDRESS_EXTRA = "address"; - public static final String LOCALE_EXTRA = "locale_extra"; + public static final String RECIPIENT_EXTRA = "recipient_id"; + public static final String LOCALE_EXTRA = "locale_extra"; protected TextView noMedia; protected Recipient recipient; @@ -206,13 +204,13 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { public void onCreate(Bundle bundle) { super.onCreate(bundle); - String address = getArguments().getString(ADDRESS_EXTRA); - Locale locale = (Locale)getArguments().getSerializable(LOCALE_EXTRA); + RecipientId recipientId = getArguments().getParcelable(RECIPIENT_EXTRA); + Locale locale = (Locale)getArguments().getSerializable(LOCALE_EXTRA); - if (address == null) throw new AssertionError(); + if (recipientId == null) throw new AssertionError(); if (locale == null) throw new AssertionError(); - this.recipient = Recipient.from(getContext(), Address.fromSerialized(address), true); + this.recipient = Recipient.live(recipientId).get(); this.locale = locale; getLoaderManager().initLoader(0, null, this); @@ -258,7 +256,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { @Override public @NonNull Loader onCreateLoader(int i, Bundle bundle) { - return new BucketedThreadMediaLoader(getContext(), recipient.getAddress()); + return new BucketedThreadMediaLoader(getContext(), recipient.getId()); } @Override @@ -308,7 +306,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { Intent intent = new Intent(context, MediaPreviewActivity.class); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate()); intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize()); - intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, mediaRecord.isOutgoing()); intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, true); @@ -497,13 +495,13 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { @Override public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new ThreadMediaLoader(getContext(), recipient.getAddress(), false); + return new ThreadMediaLoader(requireContext(), recipient.getId(), false); } @Override public void onLoadFinished(@NonNull Loader loader, Cursor data) { ((CursorRecyclerViewAdapter)this.recyclerView.getAdapter()).changeCursor(data); - getActivity().invalidateOptionsMenu(); + requireActivity().invalidateOptionsMenu(); this.noMedia.setVisibility(data.getCount() > 0 ? View.GONE : View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index 2c317e8eda..f738df6c51 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -63,12 +63,11 @@ import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; -import org.thoughtcrime.securesms.util.Util; import java.util.HashMap; import java.util.Locale; @@ -78,15 +77,14 @@ import java.util.Map; * Activity for displaying media attachments in-app */ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActivity - implements RecipientModifiedListener, - LoaderManager.LoaderCallbacks>, + implements LoaderManager.LoaderCallbacks>, MediaRailAdapter.RailItemListener, MediaPreviewFragment.Events { private final static String TAG = MediaPreviewActivity.class.getSimpleName(); - public static final String ADDRESS_EXTRA = "address"; + public static final String RECIPIENT_EXTRA = "recipient_id"; public static final String DATE_EXTRA = "date"; public static final String SIZE_EXTRA = "size"; public static final String CAPTION_EXTRA = "caption"; @@ -138,11 +136,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } - @Override - public void onModified(Recipient recipient) { - Util.runOnMain(this::initializeActionBar); - } - @Override public void onRailItemClicked(int distanceFromActive) { mediaPager.setCurrentItem(mediaPager.getCurrentItem() + distanceFromActive); @@ -223,7 +216,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv } private void initializeResources() { - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); + RecipientId recipientId = getIntent().getParcelableExtra(RECIPIENT_EXTRA); initialMediaUri = getIntent().getData(); initialMediaType = getIntent().getType(); @@ -232,8 +225,8 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false); restartItem = -1; - if (address != null) { - conversationRecipient = Recipient.from(this, address, true); + if (recipientId != null) { + conversationRecipient = Recipient.live(recipientId).get(); } else { conversationRecipient = null; } @@ -305,7 +298,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv private void showOverview() { Intent intent = new Intent(this, MediaOverviewActivity.class); - intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); + intent.putExtra(MediaOverviewActivity.RECIPIENT_EXTRA, conversationRecipient.getId()); startActivity(intent); } @@ -491,7 +484,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv if (adapter != null) { MediaItem item = adapter.getMediaItemFor(position); - if (item.recipient != null) item.recipient.addListener(MediaPreviewActivity.this); + if (item.recipient != null) item.recipient.live().observe(MediaPreviewActivity.this, r -> initializeActionBar()); viewModel.setActiveAlbumRailItem(MediaPreviewActivity.this, position); initializeActionBar(); } @@ -504,7 +497,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv if (adapter != null) { MediaItem item = adapter.getMediaItemFor(position); - if (item.recipient != null) item.recipient.removeListener(MediaPreviewActivity.this); + if (item.recipient != null) item.recipient.live().removeObservers(MediaPreviewActivity.this); adapter.pause(position); } @@ -682,11 +675,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv public MediaItem getMediaItemFor(int position) { cursor.moveToPosition(getCursorPosition(position)); MediaRecord mediaRecord = MediaRecord.from(context, cursor); - Address address = mediaRecord.getAddress(); + RecipientId recipientId = mediaRecord.getRecipientId(); if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); - return new MediaItem(address != null ? Recipient.from(context, address,true) : null, + return new MediaItem(Recipient.live(recipientId).get(), mediaRecord.getAttachment(), mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType(), diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index 6a08bcbd75..a89cdd2289 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -52,8 +52,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DynamicLanguage; @@ -73,14 +73,14 @@ import java.util.Locale; /** * @author Jake McGinty */ -public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks, RecipientModifiedListener { +public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks { private final static String TAG = MessageDetailsActivity.class.getSimpleName(); - public final static String MESSAGE_ID_EXTRA = "message_id"; - public final static String THREAD_ID_EXTRA = "thread_id"; - public final static String IS_PUSH_GROUP_EXTRA = "is_push_group"; - public final static String TYPE_EXTRA = "type"; - public final static String ADDRESS_EXTRA = "address"; + public static final String MESSAGE_ID_EXTRA = "message_id"; + public static final String THREAD_ID_EXTRA = "thread_id"; + public static final String IS_PUSH_GROUP_EXTRA = "is_push_group"; + public static final String TYPE_EXTRA = "type"; + public static final String RECIPIENT_EXTRA = "recipient_id"; private GlideRequests glideRequests; private long threadId; @@ -149,10 +149,10 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity assert getSupportActionBar() != null; getSupportActionBar().setDisplayHomeAsUpEnabled(true); - Recipient recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true); - recipient.addListener(this); + LiveRecipient recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA)); + recipient.observe(this, r -> setActionBarColor(r.getColor())); - setActionBarColor(recipient.getColor()); + setActionBarColor(recipient.get().getColor()); } private void setActionBarColor(MaterialColor color) { @@ -164,11 +164,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity } } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setActionBarColor(recipient.getColor())); - } - private void initializeResources() { inflater = LayoutInflater.from(this); View header = inflater.inflate(R.layout.message_details_header, recipientsList, false); @@ -366,20 +361,20 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity List recipients = new LinkedList<>(); - if (!messageRecord.getRecipient().isGroupRecipient()) { + if (!messageRecord.getRecipient().isGroup()) { recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), messageRecord.isUnidentified(), -1)); } else { List receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId()); if (receiptInfoList.isEmpty()) { - List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false); + List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireAddress().toGroupString(), false); for (Recipient recipient : group) { recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1)); } } else { for (GroupReceiptInfo info : receiptInfoList) { - recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true), + recipients.add(new RecipientDeliveryStatus(Recipient.resolved(info.getRecipientId()), getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()), info.isUnidentified(), info.getTimestamp())); diff --git a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java index 004a27367a..6b20a14100 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java @@ -49,7 +49,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView. @Override public long getItemId(int position) { try { - return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.getAddress().serialize().getBytes())); + return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.requireAddress().serialize().getBytes())); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } diff --git a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java index 8b5bc8b83f..7363d077b0 100644 --- a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java +++ b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java @@ -25,6 +25,8 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.DeliveryStatusView; @@ -34,9 +36,8 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; /** * A simple view to show the recipients of a message @@ -44,7 +45,7 @@ import org.thoughtcrime.securesms.util.Util; * @author Jake McGinty */ public class MessageRecipientListItem extends RelativeLayout - implements RecipientModifiedListener + implements RecipientForeverObserver { @SuppressWarnings("unused") private final static String TAG = MessageRecipientListItem.class.getSimpleName(); @@ -84,10 +85,12 @@ public class MessageRecipientListItem extends RelativeLayout final RecipientDeliveryStatus member, final boolean isPushGroup) { + if (this.member != null) this.member.getRecipient().live().removeForeverObserver(this); + this.glideRequests = glideRequests; this.member = member; - member.getRecipient().addListener(this); + member.getRecipient().live().observeForever(this); fromView.setText(member.getRecipient()); contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false); setIssueIndicators(record, isPushGroup); @@ -138,7 +141,7 @@ public class MessageRecipientListItem extends RelativeLayout private NetworkFailure getNetworkFailure(final MessageRecord record) { if (record.hasNetworkFailures()) { for (final NetworkFailure failure : record.getNetworkFailures()) { - if (failure.getAddress().equals(member.getRecipient().getAddress())) { + if (failure.getRecipientId(getContext()).equals(member.getRecipient().getId())) { return failure; } } @@ -149,7 +152,7 @@ public class MessageRecipientListItem extends RelativeLayout private IdentityKeyMismatch getKeyMismatch(final MessageRecord record) { if (record.isIdentityMismatchFailure()) { for (final IdentityKeyMismatch mismatch : record.getIdentityKeyMismatches()) { - if (mismatch.getAddress().equals(member.getRecipient().getAddress())) { + if (mismatch.getRecipientId(getContext()).equals(member.getRecipient().getId())) { return mismatch; } } @@ -158,14 +161,12 @@ public class MessageRecipientListItem extends RelativeLayout } public void unbind() { - if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().removeListener(this); + if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().live().removeForeverObserver(this); } @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> { - fromView.setText(recipient); - contactPhotoImage.setAvatar(glideRequests, recipient, false); - }); + public void onRecipientChanged(@NonNull Recipient recipient) { + fromView.setText(recipient); + contactPhotoImage.setAvatar(glideRequests, recipient, false); } } diff --git a/src/org/thoughtcrime/securesms/NewConversationActivity.java b/src/org/thoughtcrime/securesms/NewConversationActivity.java index 54c8729d11..1e87f52f62 100644 --- a/src/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/src/org/thoughtcrime/securesms/NewConversationActivity.java @@ -51,10 +51,10 @@ public class NewConversationActivity extends ContactSelectionActivity @Override public void onContactSelected(String number) { - Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true); + Recipient recipient = Recipient.external(this, number); Intent intent = new Intent(this, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)); intent.setDataAndType(getIntent().getData(), getIntent().getType()); diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index c281661b66..d3050c6fe0 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -67,8 +67,9 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment; import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference; import org.thoughtcrime.securesms.preferences.widgets.ContactPreference; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.Dialogs; @@ -78,7 +79,6 @@ import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.whispersystems.libsignal.util.guava.Optional; @@ -86,11 +86,11 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.util.concurrent.ExecutionException; @SuppressLint("StaticFieldLeak") -public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener, LoaderManager.LoaderCallbacks +public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks { private static final String TAG = RecipientPreferenceActivity.class.getSimpleName(); - public static final String ADDRESS_EXTRA = "recipient_address"; + public static final String RECIPIENT_ID = "recipient_address"; public static final String CAN_HAVE_SAFETY_NUMBER_EXTRA = "can_have_safety_number"; private static final String PREFERENCE_MUTED = "pref_key_recipient_mute"; @@ -109,7 +109,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi private ImageView avatar; private GlideRequests glideRequests; - private Address address; + private RecipientId recipientId; private TextView threadPhotoRailLabel; private ThreadPhotoRailView threadPhotoRailView; private CollapsingToolbarLayout toolbarLayout; @@ -124,13 +124,13 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi public void onCreate(Bundle instanceState, boolean ready) { setContentView(R.layout.recipient_preference_activity); this.glideRequests = GlideApp.with(this); - this.address = getIntent().getParcelableExtra(ADDRESS_EXTRA); + this.recipientId = getIntent().getParcelableExtra(RECIPIENT_ID); - Recipient recipient = Recipient.from(this, address, true); + LiveRecipient recipient = Recipient.live(recipientId); initializeToolbar(); - setHeader(recipient); - recipient.addListener(this); + setHeader(recipient.get()); + recipient.observe(this, this::setHeader); getSupportLoaderManager().initLoader(0, null, this); } @@ -178,7 +178,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi this.threadPhotoRailView.setListener(mediaRecord -> { Intent intent = new Intent(RecipientPreferenceActivity.this, MediaPreviewActivity.class); - intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, address); + intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipientId); intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, mediaRecord.isOutgoing()); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate()); intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize()); @@ -190,7 +190,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi this.threadPhotoRailLabel.setOnClickListener(v -> { Intent intent = new Intent(this, MediaOverviewActivity.class); - intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, address); + intent.putExtra(MediaOverviewActivity.RECIPIENT_EXTRA, recipientId); startActivity(intent); }); @@ -206,7 +206,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } private void setHeader(@NonNull Recipient recipient) { - ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.getAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this))) + ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.requireAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this))) : recipient.getContactPhoto(); FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large) : recipient.getFallbackContactPhoto(); @@ -225,14 +225,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi this.toolbarLayout.setContentScrimColor(recipient.getColor().toActionBarColor(this)); } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setHeader(recipient)); - } - @Override public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new ThreadMediaLoader(this, address, true); + return new ThreadMediaLoader(this, recipientId, true); } @Override @@ -248,7 +243,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi this.threadPhotoRailView.setCursor(glideRequests, data); Bundle bundle = new Bundle(); - bundle.putParcelable(ADDRESS_EXTRA, address); + bundle.putParcelable(RECIPIENT_ID, recipientId); initFragment(R.id.preference_fragment, new RecipientPreferenceFragment(), null, bundle); } @@ -257,12 +252,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi this.threadPhotoRailView.setCursor(glideRequests, null); } - public static class RecipientPreferenceFragment - extends CorrectedPreferenceFragment - implements RecipientModifiedListener - { - private Recipient recipient; - private boolean canHaveSafetyNumber; + public static class RecipientPreferenceFragment extends CorrectedPreferenceFragment { + private LiveRecipient recipient; + private boolean canHaveSafetyNumber; @Override public void onCreate(Bundle icicle) { @@ -277,20 +269,20 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi Preference customNotificationsPref = this.findPreference(PREFERENCE_CUSTOM_NOTIFICATIONS); if (NotificationChannels.supported()) { - ((SwitchPreferenceCompat) customNotificationsPref).setChecked(recipient.getNotificationChannel() != null); + ((SwitchPreferenceCompat) customNotificationsPref).setChecked(recipient.get().getNotificationChannel() != null); customNotificationsPref.setOnPreferenceChangeListener(new CustomNotificationsChangedListener()); this.findPreference(PREFERENCE_MESSAGE_TONE).setDependency(PREFERENCE_CUSTOM_NOTIFICATIONS); this.findPreference(PREFERENCE_MESSAGE_VIBRATE).setDependency(PREFERENCE_CUSTOM_NOTIFICATIONS); - if (recipient.getNotificationChannel() != null) { - final Context context = getContext(); + if (recipient.get().getNotificationChannel() != null) { + final Context context = requireContext(); new AsyncTask() { @Override protected Void doInBackground(Void... voids) { RecipientDatabase db = DatabaseFactory.getRecipientDatabase(getContext()); - db.setMessageRingtone(recipient, NotificationChannels.getMessageRingtone(context, recipient)); - db.setMessageVibrate(recipient, NotificationChannels.getMessageVibrate(context, recipient) ? VibrateState.ENABLED : VibrateState.DISABLED); + db.setMessageRingtone(recipient.getId(), NotificationChannels.getMessageRingtone(context, recipient.get())); + db.setMessageVibrate(recipient.getId(), NotificationChannels.getMessageVibrate(context, recipient.get()) ? VibrateState.ENABLED : VibrateState.DISABLED); NotificationChannels.ensureCustomChannelConsistency(context); return null; } @@ -336,13 +328,12 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override public void onResume() { super.onResume(); - setSummaries(recipient); + setSummaries(recipient.get()); } @Override public void onDestroy() { super.onDestroy(); - this.recipient.removeListener(this); } @Override @@ -359,8 +350,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } private void initializeRecipients() { - this.recipient = Recipient.from(getActivity(), getArguments().getParcelable(ADDRESS_EXTRA), true); - this.recipient.addListener(this); + this.recipient = Recipient.live(getArguments().getParcelable(RECIPIENT_ID)); + this.recipient.observe(this, this::setSummaries); } private void setSummaries(Recipient recipient) { @@ -406,7 +397,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi if (privacyCategory != null) privacyCategory.setVisible(false); if (divider != null) divider.setVisible(false); if (callCategory != null) callCategory.setVisible(false); - } if (recipient.isGroupRecipient()) { + } if (recipient.isGroup()) { if (colorPreference != null) colorPreference.setVisible(false); if (identityPreference != null) identityPreference.setVisible(false); if (callCategory != null) callCategory.setVisible(false); @@ -417,7 +408,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(getActivity())); colorPreference.setColor(recipient.getColor().toActionBarColor(getActivity())); - aboutPreference.setTitle(formatAddress(recipient.getAddress())); + aboutPreference.setTitle(formatAddress(recipient.requireAddress())); aboutPreference.setSummary(recipient.getCustomLabel()); aboutPreference.setSecure(recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED); @@ -478,15 +469,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> { - if (getContext() != null && getActivity() != null && !getActivity().isFinishing()) { - setSummaries(recipient); - } - }); - } - private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener { private final boolean calls; @@ -514,10 +496,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override protected Void doInBackground(Uri... params) { if (calls) { - DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipient, params[0]); + DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipient.getId(), params[0]); } else { - DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient, params[0]); - NotificationChannels.updateMessageRingtone(context, recipient, params[0]); + DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.getId(), params[0]); + NotificationChannels.updateMessageRingtone(context, recipient.get(), params[0]); } return null; } @@ -541,10 +523,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi Uri defaultUri; if (calls) { - current = recipient.getCallRingtone(); + current = recipient.get().getCallRingtone(); defaultUri = TextSecurePreferences.getCallNotificationRingtone(getContext()); } else { - current = recipient.getMessageRingtone(); + current = recipient.get().getMessageRingtone(); defaultUri = TextSecurePreferences.getNotificationRingtone(getContext()); } @@ -582,11 +564,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override protected Void doInBackground(Void... params) { if (call) { - DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipient, vibrateState); + DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipient.getId(), vibrateState); } else { - DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient, vibrateState); - NotificationChannels.updateMessageVibrate(context, recipient, vibrateState); + DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient.getId(), vibrateState); + NotificationChannels.updateMessageVibrate(context, recipient.get(), vibrateState); } return null; } @@ -605,7 +587,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi final int value = (Integer) newValue; final MaterialColor selectedColor = MaterialColors.CONVERSATION_PALETTE.getByColor(context, value); - final MaterialColor currentColor = recipient.getColor(); + final MaterialColor currentColor = recipient.get().getColor(); if (selectedColor == null) return true; @@ -613,12 +595,12 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(context).setColor(recipient, selectedColor); + DatabaseFactory.getRecipientDatabase(context).setColor(recipient.getId(), selectedColor); - if (recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { + if (recipient.get().resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { ApplicationContext.getInstance(context) .getJobManager() - .add(new MultiDeviceContactUpdateJob(context, recipient.getAddress())); + .add(new MultiDeviceContactUpdateJob(recipient.getId())); } return null; } @@ -632,30 +614,28 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override public boolean onPreferenceClick(Preference preference) { - if (recipient.isMuted()) handleUnmute(preference.getContext()); - else handleMute(preference.getContext()); + if (recipient.get().isMuted()) handleUnmute(preference.getContext()); + else handleMute(preference.getContext()); return true; } private void handleMute(@NonNull Context context) { - MuteDialog.show(context, until -> setMuted(context, recipient, until)); + MuteDialog.show(context, until -> setMuted(context, recipient.get(), until)); - setSummaries(recipient); + setSummaries(recipient.get()); } private void handleUnmute(@NonNull Context context) { - setMuted(context, recipient, 0); + setMuted(context, recipient.get(), 0); } private void setMuted(@NonNull final Context context, final Recipient recipient, final long until) { - recipient.setMuted(until); - new AsyncTask() { @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(context) - .setMuted(recipient, until); + .setMuted(recipient.getId(), until); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -674,7 +654,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override public boolean onPreferenceClick(Preference preference) { Intent verifyIdentityIntent = new Intent(preference.getContext(), VerifyIdentityActivity.class); - verifyIdentityIntent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, recipient.getAddress()); + verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_EXTRA, recipient.getId()); verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey())); verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED); startActivity(verifyIdentityIntent); @@ -686,8 +666,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi private class BlockClickedListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { - if (recipient.isBlocked()) handleUnblock(preference.getContext()); - else handleBlock(preference.getContext()); + if (recipient.get().isBlocked()) handleUnblock(preference.getContext()); + else handleBlock(preference.getContext()); return true; } @@ -700,10 +680,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question; int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact; - if (recipient.isGroupRecipient()) { + if (recipient.get().isGroup()) { bodyRes = R.string.RecipientPreferenceActivity_block_and_leave_group_description; - if (recipient.isGroupRecipient() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.getAddress().toGroupString())) { + if (recipient.get().isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.get().requireAddress().toGroupString())) { titleRes = R.string.RecipientPreferenceActivity_block_and_leave_group; } else { titleRes = R.string.RecipientPreferenceActivity_block_group; @@ -721,7 +701,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi .setCancelable(true) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> { - setBlocked(context, recipient, true); + setBlocked(context, recipient.get(), true); }).show(); } }.execute(); @@ -731,7 +711,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi int titleRes = R.string.RecipientPreferenceActivity_unblock_this_contact_question; int bodyRes = R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; - if (recipient.isGroupRecipient()) { + if (recipient.resolve().isGroup()) { titleRes = R.string.RecipientPreferenceActivity_unblock_this_group_question; bodyRes = R.string.RecipientPreferenceActivity_unblock_this_group_description; } @@ -741,7 +721,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi .setMessage(bodyRes) .setCancelable(true) .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.RecipientPreferenceActivity_unblock, (dialog, which) -> setBlocked(context, recipient, false)).show(); + .setPositiveButton(R.string.RecipientPreferenceActivity_unblock, (dialog, which) -> setBlocked(context, recipient.get(), false)).show(); } private void setBlocked(@NonNull final Context context, final Recipient recipient, final boolean blocked) { @@ -749,9 +729,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(context) - .setBlocked(recipient, blocked); + .setBlocked(recipient.getId(), blocked); - if (recipient.isGroupRecipient() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.getAddress().toGroupString())) { + if (recipient.isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireAddress().toGroupString())) { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); Optional leaveMessage = GroupUtil.createGroupLeaveMessage(context, recipient); @@ -759,9 +739,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi MessageSender.send(context, leaveMessage.get(), threadId, false, null); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - String groupId = recipient.getAddress().toGroupString(); + String groupId = recipient.requireAddress().toGroupString(); groupDatabase.setActive(groupId, false); - groupDatabase.remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); + groupDatabase.remove(groupId, Recipient.self().getId()); } else { Log.w(TAG, "Failed to leave group. Can't block."); Toast.makeText(context, R.string.RecipientPreferenceActivity_error_leaving_group, Toast.LENGTH_LONG).show(); @@ -787,19 +767,19 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override public void onMessageClicked() { - CommunicationActions.startConversation(getContext(), recipient, null); + CommunicationActions.startConversation(getContext(), recipient.get(), null); } @Override public void onSecureCallClicked() { - CommunicationActions.startVoiceCall(getActivity(), recipient); + CommunicationActions.startVoiceCall(getActivity(), recipient.get()); } @Override public void onInSecureCallClicked() { try { Intent dialIntent = new Intent(Intent.ACTION_DIAL, - Uri.parse("tel:" + recipient.getAddress().serialize())); + Uri.parse("tel:" + recipient.get().requireAddress().serialize())); startActivity(dialIntent); } catch (ActivityNotFoundException anfe) { Log.w(TAG, anfe); @@ -821,11 +801,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi @Override protected Void doInBackground(Void... params) { if (enabled) { - String channel = NotificationChannels.createChannelFor(context, recipient); - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient, channel); + String channel = NotificationChannels.createChannelFor(context, recipient.get()); + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), channel); } else { - NotificationChannels.deleteChannelFor(context, recipient); - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient, null); + NotificationChannels.deleteChannelFor(context, recipient.get()); + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), null); } return null; } diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index 0147db7b01..9849a79e34 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.push.AccountManagerFactory; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.registration.CaptchaActivity; import org.thoughtcrime.securesms.registration.PushChallengeRequest; import org.thoughtcrime.securesms.service.DirectoryRefreshListener; @@ -735,7 +736,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif TextSecurePreferences.setWebsocketRegistered(RegistrationActivity.this, true); DatabaseFactory.getIdentityDatabase(RegistrationActivity.this) - .saveIdentity(Address.fromSerialized(registrationState.e164number), + .saveIdentity(Recipient.external(RegistrationActivity.this, registrationState.e164number).getId(), identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true); diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 0bd2c873bd..6aba83411f 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; @@ -55,6 +56,8 @@ import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.util.concurrent.SimpleTask; +import org.whispersystems.libsignal.util.Pair; import java.io.FileInputStream; import java.io.IOException; @@ -72,7 +75,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity private static final String TAG = ShareActivity.class.getSimpleName(); public static final String EXTRA_THREAD_ID = "thread_id"; - public static final String EXTRA_ADDRESS_MARSHALLED = "address_marshalled"; + public static final String EXTRA_RECIPIENT_ID = "recipient_id"; public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type"; private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); @@ -216,35 +219,30 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity } private void handleResolvedMedia(Intent intent, boolean animate) { - long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1); - int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1); - Address address = null; + long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1); + int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1); + RecipientId recipientId = null; - if (intent.hasExtra(EXTRA_ADDRESS_MARSHALLED)) { - Parcel parcel = Parcel.obtain(); - byte[] marshalled = intent.getByteArrayExtra(EXTRA_ADDRESS_MARSHALLED); - parcel.unmarshall(marshalled, 0, marshalled.length); - parcel.setDataPosition(0); - address = parcel.readParcelable(getClassLoader()); - parcel.recycle(); + if (intent.hasExtra(EXTRA_RECIPIENT_ID)) { + recipientId = RecipientId.from(intent.getStringExtra(EXTRA_RECIPIENT_ID)); } - boolean hasResolvedDestination = threadId != -1 && address != null && distributionType != -1; + boolean hasResolvedDestination = threadId != -1 && recipientId != null && distributionType != -1; - if (!hasResolvedDestination && animate) { - ViewUtil.fadeIn(contactsFragment.getView(), 300); + if (hasResolvedDestination) { + createConversation(threadId, recipientId, distributionType); + } else if (animate) { + ViewUtil.fadeIn(contactsFragment.requireView(), 300); ViewUtil.fadeOut(progressWheel, 300); - } else if (!hasResolvedDestination) { - contactsFragment.getView().setVisibility(View.VISIBLE); - progressWheel.setVisibility(View.GONE); } else { - createConversation(threadId, address, distributionType); + contactsFragment.requireView().setVisibility(View.VISIBLE); + progressWheel.setVisibility(View.GONE); } } - private void createConversation(long threadId, Address address, int distributionType) { + private void createConversation(long threadId, @NonNull RecipientId recipientId, int distributionType) { final Intent intent = getBaseShareIntent(ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, address); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipientId); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); @@ -277,9 +275,14 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity @Override public void onContactSelected(String number) { - Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true); - long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient); - createConversation(existingThread, recipient.getAddress(), ThreadDatabase.DistributionTypes.DEFAULT); + SimpleTask.run(this.getLifecycle(), () -> { + Recipient recipient = Recipient.external(this, number); + long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient); + return new Pair<>(existingThread, recipient); + }, result -> { + createConversation(result.first(), result.second().getId(), ThreadDatabase.DistributionTypes.DEFAULT); + }); + } @Override diff --git a/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java b/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java index a35df58126..19b5f10c41 100644 --- a/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java +++ b/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms; -import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -10,38 +9,36 @@ import androidx.core.app.TaskStackBuilder; import androidx.appcompat.app.AppCompatActivity; import android.widget.Toast; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.CommunicationActions; public class ShortcutLauncherActivity extends AppCompatActivity { - private static final String KEY_SERIALIZED_ADDRESS = "serialized_address"; + private static final String KEY_RECIPIENT = "recipient_id"; - public static Intent createIntent(@NonNull Context context, @NonNull Address address) { + public static Intent createIntent(@NonNull Context context, @NonNull RecipientId recipientId) { Intent intent = new Intent(context, ShortcutLauncherActivity.class); intent.setAction(Intent.ACTION_MAIN); - intent.putExtra(KEY_SERIALIZED_ADDRESS, address.serialize()); + intent.putExtra(KEY_RECIPIENT, recipientId.serialize()); return intent; } - @SuppressLint("StaticFieldLeak") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - String serializedAddress = getIntent().getStringExtra(KEY_SERIALIZED_ADDRESS); + String rawId = getIntent().getStringExtra(KEY_RECIPIENT); - if (serializedAddress == null) { + if (rawId == null) { Toast.makeText(this, R.string.ShortcutLauncherActivity_invalid_shortcut, Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, ConversationListActivity.class)); finish(); return; } - Address address = Address.fromSerialized(serializedAddress); - Recipient recipient = Recipient.from(this, address, true); + Recipient recipient = Recipient.live(RecipientId.from(rawId)).get(); TaskStackBuilder backStack = TaskStackBuilder.create(this) .addNextIntent(new Intent(this, ConversationListActivity.class)); diff --git a/src/org/thoughtcrime/securesms/SmsSendtoActivity.java b/src/org/thoughtcrime/securesms/SmsSendtoActivity.java index c6602cce3f..75326ba5c8 100644 --- a/src/org/thoughtcrime/securesms/SmsSendtoActivity.java +++ b/src/org/thoughtcrime/securesms/SmsSendtoActivity.java @@ -48,13 +48,13 @@ public class SmsSendtoActivity extends Activity { nextIntent.putExtra(ConversationActivity.TEXT_EXTRA, destination.getBody()); Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show(); } else { - Recipient recipient = Recipient.from(this, Address.fromExternal(this, destination.getDestination()), true); + Recipient recipient = Recipient.external(this, destination.getDestination()); long threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient); nextIntent = new Intent(this, ConversationActivity.class); nextIntent.putExtra(ConversationActivity.TEXT_EXTRA, destination.getBody()); nextIntent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); - nextIntent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + nextIntent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); } return nextIntent; } diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index cc425b167b..02ce4fc9c6 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.components.camera.CameraView; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob; @@ -72,8 +71,9 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.qr.QrCode; import org.thoughtcrime.securesms.qr.ScanListener; import org.thoughtcrime.securesms.qr.ScanningThread; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.IdentityUtil; @@ -98,13 +98,13 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; * @author Moxie Marlinspike */ @SuppressLint("StaticFieldLeak") -public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener, ScanListener, View.OnClickListener { +public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements ScanListener, View.OnClickListener { private static final String TAG = VerifyIdentityActivity.class.getSimpleName(); - public static final String ADDRESS_EXTRA = "address"; - public static final String IDENTITY_EXTRA = "recipient_identity"; - public static final String VERIFIED_EXTRA = "verified_state"; + public static final String RECIPIENT_EXTRA = "recipient_id"; + public static final String IDENTITY_EXTRA = "recipient_identity"; + public static final String VERIFIED_EXTRA = "verified_state"; private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); @@ -123,15 +123,14 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number); - Recipient recipient = Recipient.from(this, (Address)getIntent().getParcelableExtra(ADDRESS_EXTRA), true); - recipient.addListener(this); + LiveRecipient recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA)); + recipient.observe(this, r -> setActionBarNotificationBarColor(r.getColor())); - setActionBarNotificationBarColor(recipient.getColor()); + setActionBarNotificationBarColor(recipient.get().getColor()); Bundle extras = new Bundle(); - extras.putParcelable(VerifyDisplayFragment.REMOTE_ADDRESS, getIntent().getParcelableExtra(ADDRESS_EXTRA)); + extras.putParcelable(VerifyDisplayFragment.RECIPIENT_ID, getIntent().getParcelableExtra(RECIPIENT_EXTRA)); extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA)); - extras.putString(VerifyDisplayFragment.REMOTE_NUMBER, recipient.getAddress().toPhoneString()); extras.putParcelable(VerifyDisplayFragment.LOCAL_IDENTITY, new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this))); extras.putString(VerifyDisplayFragment.LOCAL_NUMBER, TextSecurePreferences.getLocalNumber(this)); extras.putBoolean(VerifyDisplayFragment.VERIFIED_STATE, getIntent().getBooleanExtra(VERIFIED_EXTRA, false)); @@ -151,11 +150,6 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity return false; } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setActionBarNotificationBarColor(recipient.getColor())); - } - @Override public void onQrDataFound(final String data) { Util.runOnMain(() -> { @@ -198,18 +192,18 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity } } - public static class VerifyDisplayFragment extends Fragment implements RecipientModifiedListener, CompoundButton.OnCheckedChangeListener { + public static class VerifyDisplayFragment extends Fragment implements CompoundButton.OnCheckedChangeListener { - public static final String REMOTE_ADDRESS = "remote_address"; + public static final String RECIPIENT_ID = "recipient_id"; public static final String REMOTE_NUMBER = "remote_number"; public static final String REMOTE_IDENTITY = "remote_identity"; public static final String LOCAL_IDENTITY = "local_identity"; public static final String LOCAL_NUMBER = "local_number"; public static final String VERIFIED_STATE = "verified_state"; - private Recipient recipient; - private String localNumber; - private String remoteNumber; + private LiveRecipient recipient; + private String localNumber; + private String remoteNumber; private IdentityKey localIdentity; private IdentityKey remoteIdentity; @@ -264,21 +258,21 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity public void onCreate(Bundle bundle) { super.onCreate(bundle); - Address address = getArguments().getParcelable(REMOTE_ADDRESS); + RecipientId recipientId = getArguments().getParcelable(RECIPIENT_ID); IdentityKeyParcelable localIdentityParcelable = getArguments().getParcelable(LOCAL_IDENTITY); IdentityKeyParcelable remoteIdentityParcelable = getArguments().getParcelable(REMOTE_IDENTITY); - if (address == null) throw new AssertionError("Address required"); + if (recipientId == null) throw new AssertionError("RecipientId required"); if (localIdentityParcelable == null) throw new AssertionError("local identity required"); if (remoteIdentityParcelable == null) throw new AssertionError("remote identity required"); this.localNumber = getArguments().getString(LOCAL_NUMBER); this.localIdentity = localIdentityParcelable.get(); - this.remoteNumber = getArguments().getString(REMOTE_NUMBER); - this.recipient = Recipient.from(getActivity(), address, true); + this.recipient = Recipient.live(recipientId); + this.remoteNumber = recipient.get().requireAddress().serialize(); this.remoteIdentity = remoteIdentityParcelable.get(); - this.recipient.addListener(this); + this.recipient.observe(this, this::setRecipientText); new AsyncTask() { @Override @@ -298,16 +292,11 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity setHasOptionsMenu(true); } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setRecipientText(recipient)); - } - @Override public void onResume() { super.onResume(); - setRecipientText(recipient); + setRecipientText(recipient.get()); if (fingerprint != null) { setFingerprintViews(fingerprint, false); @@ -322,12 +311,6 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity } } - @Override - public void onDestroy() { - super.onDestroy(); - recipient.removeListener(this); - } - @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) @@ -587,31 +570,31 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Recipient... params) { synchronized (SESSION_LOCK) { if (isChecked) { - Log.i(TAG, "Saving identity: " + params[0].getAddress()); + Log.i(TAG, "Saving identity: " + params[0].requireAddress()); DatabaseFactory.getIdentityDatabase(getActivity()) - .saveIdentity(params[0].getAddress(), + .saveIdentity(params[0].getId(), remoteIdentity, VerifiedStatus.VERIFIED, false, System.currentTimeMillis(), true); } else { DatabaseFactory.getIdentityDatabase(getActivity()) - .setVerified(params[0].getAddress(), + .setVerified(params[0].getId(), remoteIdentity, VerifiedStatus.DEFAULT); } ApplicationContext.getInstance(getActivity()) .getJobManager() - .add(new MultiDeviceVerifiedUpdateJob(recipient.getAddress(), + .add(new MultiDeviceVerifiedUpdateJob(recipient.getId(), remoteIdentity, isChecked ? VerifiedStatus.VERIFIED : VerifiedStatus.DEFAULT)); - IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false); + IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false); } return null; } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get()); } } diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java index ca87ce328f..6b437a0100 100644 --- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -26,7 +26,6 @@ import android.content.res.Configuration; import android.media.AudioManager; import android.os.Bundle; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import org.thoughtcrime.securesms.logging.Log; import android.view.View; @@ -47,7 +46,6 @@ import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; -import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.SignalProtocolAddress; @@ -280,11 +278,11 @@ public class WebRtcCallActivity extends Activity { public void onClick(View v) { synchronized (SESSION_LOCK) { TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this); - identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.getAddress().serialize(), 1), theirIdentity, true); + identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.requireAddress().serialize(), 1), theirIdentity, true); } Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, recipient.getAddress()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId()); intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL); startService(intent); } diff --git a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java index 01aeb29fa2..6baf2fa274 100644 --- a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java +++ b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.SearchDatabase; import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.Util; @@ -162,7 +163,7 @@ public class FullBackupImporter extends FullBackupBase { } private static void processAvatar(@NonNull Context context, @NonNull BackupProtos.Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException { - inputStream.readAttachmentTo(new FileOutputStream(AvatarHelper.getAvatarFile(context, Address.fromExternal(context, avatar.getName()))), avatar.getLength()); + inputStream.readAttachmentTo(new FileOutputStream(AvatarHelper.getAvatarFile(context, Address.fromSerialized(PhoneNumberFormatter.get(context).format(avatar.getName())))), avatar.getLength()); } @SuppressLint("ApplySharedPref") diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index becf386e91..92b6e85650 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -126,7 +126,7 @@ public final class AvatarImageView extends AppCompatImageView { } private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) { - if (!recipient.isGroupRecipient() && quickContactEnabled) { + if (!recipient.isGroup() && quickContactEnabled) { super.setOnClickListener(v -> { if (recipient.getContactUri() != null) { ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); diff --git a/src/org/thoughtcrime/securesms/components/InputPanel.java b/src/org/thoughtcrime/securesms/components/InputPanel.java index b401fd5cac..3fc35bd84b 100644 --- a/src/org/thoughtcrime/securesms/components/InputPanel.java +++ b/src/org/thoughtcrime/securesms/components/InputPanel.java @@ -171,7 +171,7 @@ public class InputPanel extends LinearLayout public Optional getQuote() { if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) { - return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getAddress(), quoteView.getBody(), false, quoteView.getAttachments())); + return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody(), false, quoteView.getAttachments())); } else { return Optional.absent(); } diff --git a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java index e9494f9157..57e34f4c50 100644 --- a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java +++ b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java @@ -26,12 +26,14 @@ import android.view.View; import android.widget.AdapterView; import android.widget.RelativeLayout; +import com.annimon.stream.Stream; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.RecipientsAdapter; import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import java.util.LinkedList; import java.util.List; @@ -43,7 +45,7 @@ import java.util.StringTokenizer; * * @author Moxie Marlinspike */ -public class PushRecipientsPanel extends RelativeLayout implements RecipientModifiedListener { +public class PushRecipientsPanel extends RelativeLayout implements RecipientForeverObserver { private final String TAG = PushRecipientsPanel.class.getSimpleName(); private RecipientsPanelChangedListener panelChangeListener; @@ -67,9 +69,15 @@ public class PushRecipientsPanel extends RelativeLayout implements RecipientModi initialize(); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + Stream.of(getRecipients()).map(Recipient::live).forEach(r -> r.removeForeverObserver(this)); + } + public List getRecipients() { String rawText = recipientsText.getText().toString(); - return getRecipientsFromString(getContext(), rawText, true); + return getRecipientsFromString(getContext(), rawText); } public void disable() { @@ -98,9 +106,7 @@ public class PushRecipientsPanel extends RelativeLayout implements RecipientModi List recipients = getRecipients(); - for (Recipient recipient : recipients) { - recipient.addListener(this); - } + Stream.of(recipients).map(Recipient::live).forEach(r -> r.observeForever(this)); recipientsText.setAdapter(new RecipientsAdapter(this.getContext())); recipientsText.populate(recipients); @@ -117,7 +123,7 @@ public class PushRecipientsPanel extends RelativeLayout implements RecipientModi }); } - private @NonNull List getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) { + private @NonNull List getRecipientsFromString(Context context, @NonNull String rawText) { StringTokenizer tokenizer = new StringTokenizer(rawText, ","); List recipients = new LinkedList<>(); @@ -125,8 +131,8 @@ public class PushRecipientsPanel extends RelativeLayout implements RecipientModi String token = tokenizer.nextToken().trim(); if (!TextUtils.isEmpty(token)) { - if (hasBracketedNumber(token)) recipients.add(Recipient.from(context, Address.fromExternal(context, parseBracketedNumber(token)), asynchronous)); - else recipients.add(Recipient.from(context, Address.fromExternal(context, token), asynchronous)); + if (hasBracketedNumber(token)) recipients.add(Recipient.external(context, parseBracketedNumber(token))); + else recipients.add(Recipient.external(context, token)); } } @@ -149,7 +155,7 @@ public class PushRecipientsPanel extends RelativeLayout implements RecipientModi } @Override - public void onModified(Recipient recipient) { + public void onRecipientChanged(@NonNull Recipient recipient) { recipientsText.populate(getRecipients()); } diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java index 57a59e7d1b..1d23872030 100644 --- a/src/org/thoughtcrime/securesms/components/QuoteView.java +++ b/src/org/thoughtcrime/securesms/components/QuoteView.java @@ -26,14 +26,15 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import java.util.List; -public class QuoteView extends FrameLayout implements RecipientModifiedListener { +public class QuoteView extends FrameLayout implements RecipientForeverObserver { private static final String TAG = QuoteView.class.getSimpleName(); @@ -52,16 +53,16 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener private TextView attachmentNameView; private ImageView dismissView; - private long id; - private Recipient author; - private String body; - private TextView mediaDescriptionText; - private TextView missingLinkText; - private SlideDeck attachments; - private int messageType; - private int largeCornerRadius; - private int smallCornerRadius; - private CornerMask cornerMask; + private long id; + private LiveRecipient author; + private String body; + private TextView mediaDescriptionText; + private TextView missingLinkText; + private SlideDeck attachments; + private int messageType; + private int largeCornerRadius; + private int smallCornerRadius; + private CornerMask cornerMask; public QuoteView(Context context) { @@ -137,6 +138,12 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener cornerMask.mask(canvas); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (author != null) author.removeForeverObserver(this); + } + public void setQuote(GlideRequests glideRequests, long id, @NonNull Recipient author, @@ -144,14 +151,14 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener boolean originalMissing, @NonNull SlideDeck attachments) { - if (this.author != null) this.author.removeListener(this); + if (this.author != null) this.author.removeForeverObserver(this); this.id = id; - this.author = author; + this.author = author.live(); this.body = body; this.attachments = attachments; - author.addListener(this); + this.author.observeForever(this); setQuoteAuthor(author); setQuoteText(body, attachments); setQuoteAttachment(glideRequests, attachments); @@ -164,7 +171,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener } public void dismiss() { - if (this.author != null) this.author.removeListener(this); + if (this.author != null) this.author.removeForeverObserver(this); this.id = 0; this.author = null; @@ -174,17 +181,13 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener } @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> { - if (recipient == author) { - setQuoteAuthor(recipient); - } - }); + public void onRecipientChanged(@NonNull Recipient recipient) { + setQuoteAuthor(recipient); } private void setQuoteAuthor(@NonNull Recipient author) { boolean outgoing = messageType != MESSAGE_TYPE_INCOMING; - boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress()); + boolean isOwnNumber = Util.isOwnNumber(getContext(), author.requireAddress()); authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : author.toShortString()); @@ -259,7 +262,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener private void setQuoteMissingFooter(boolean missing) { footerView.setVisibility(missing ? VISIBLE : GONE); - footerView.setBackgroundColor(author.getColor().toQuoteFooterColor(getContext(), messageType != MESSAGE_TYPE_INCOMING)); + footerView.setBackgroundColor(author.get().getColor().toQuoteFooterColor(getContext(), messageType != MESSAGE_TYPE_INCOMING)); } public long getQuoteId() { @@ -267,7 +270,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener } public Recipient getAuthor() { - return author; + return author.get(); } public String getBody() { diff --git a/src/org/thoughtcrime/securesms/components/SharedContactView.java b/src/org/thoughtcrime/securesms/components/SharedContactView.java index 33245071a1..994f2c6b4a 100644 --- a/src/org/thoughtcrime/securesms/components/SharedContactView.java +++ b/src/org/thoughtcrime/securesms/components/SharedContactView.java @@ -24,9 +24,10 @@ import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import java.util.ArrayList; import java.util.Collections; @@ -35,7 +36,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; -public class SharedContactView extends LinearLayout implements RecipientModifiedListener { +public class SharedContactView extends LinearLayout implements RecipientForeverObserver { private ImageView avatarView; private TextView nameView; @@ -51,7 +52,7 @@ public class SharedContactView extends LinearLayout implements RecipientModified private int bigCornerRadius; private int smallCornerRadius; - private final Map activeRecipients = new HashMap<>(); + private final Map activeRecipients = new HashMap<>(); public SharedContactView(Context context) { super(context); @@ -114,12 +115,16 @@ public class SharedContactView extends LinearLayout implements RecipientModified this.locale = locale; this.contact = contact; - Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeListener(this)); + Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeForeverObserver(this)); this.activeRecipients.clear(); presentContact(contact); presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getDataUri() : null); presentActionButtons(ContactUtil.getRecipients(getContext(), contact)); + + for (LiveRecipient recipient : activeRecipients.values()) { + recipient.observeForever(this); + } } public void setSingularStyle() { @@ -150,8 +155,8 @@ public class SharedContactView extends LinearLayout implements RecipientModified } @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> presentActionButtons(Collections.singletonList(recipient))); + public void onRecipientChanged(@NonNull Recipient recipient) { + presentActionButtons(Collections.singletonList(recipient.getId())); } private void presentContact(@Nullable Contact contact) { @@ -180,21 +185,19 @@ public class SharedContactView extends LinearLayout implements RecipientModified } } - private void presentActionButtons(@NonNull List recipients) { - for (Recipient recipient : recipients) { - activeRecipients.put(recipient.getAddress().serialize(), recipient); + private void presentActionButtons(@NonNull List recipients) { + for (RecipientId recipientId : recipients) { + activeRecipients.put(recipientId, Recipient.live(recipientId)); } List pushUsers = new ArrayList<>(recipients.size()); List systemUsers = new ArrayList<>(recipients.size()); - for (Recipient recipient : activeRecipients.values()) { - recipient.addListener(this); - - if (recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { - pushUsers.add(recipient); - } else if (recipient.isSystemContact()) { - systemUsers.add(recipient); + for (LiveRecipient recipient : activeRecipients.values()) { + if (recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { + pushUsers.add(recipient.get()); + } else if (recipient.get().isSystemContact()) { + systemUsers.add(recipient.get()); } } diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java b/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java index 96671d7906..1a5d280b51 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java @@ -43,7 +43,7 @@ public class TypingStatusRepository { } public synchronized void onTypingStarted(@NonNull Context context, long threadId, @NonNull Recipient author, int device) { - if (author.getAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { + if (author.requireAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { return; } @@ -67,7 +67,7 @@ public class TypingStatusRepository { } public synchronized void onTypingStopped(@NonNull Context context, long threadId, @NonNull Recipient author, int device, boolean isReplacedByIncomingMessage) { - if (author.getAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { + if (author.requireAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { return; } diff --git a/src/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java b/src/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java index 49cedfc3ee..89dcf57e9c 100644 --- a/src/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java +++ b/src/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java @@ -46,7 +46,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn protected Void doInBackground(Void... params) { synchronized (SESSION_LOCK) { for (IdentityRecord identityRecord : untrustedRecords) { - identityDatabase.setApproval(identityRecord.getAddress(), true); + identityDatabase.setApproval(identityRecord.getRecipientId(), true); } } diff --git a/src/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java b/src/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java index 3db38393ea..d1c7485efb 100644 --- a/src/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java +++ b/src/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java @@ -45,7 +45,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI protected Void doInBackground(Void... params) { synchronized (SESSION_LOCK) { for (IdentityRecord identityRecord : untrustedRecords) { - identityDatabase.setVerified(identityRecord.getAddress(), + identityDatabase.setVerified(identityRecord.getRecipientId(), identityRecord.getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT); } diff --git a/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java b/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java index 3699303bf5..32f68520db 100644 --- a/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java +++ b/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.components.reminder; +import android.annotation.SuppressLint; import android.content.Context; import android.os.AsyncTask; import androidx.annotation.NonNull; @@ -9,25 +10,19 @@ import android.view.View.OnClickListener; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; public class InviteReminder extends Reminder { + @SuppressLint("StaticFieldLeak") public InviteReminder(final @NonNull Context context, final @NonNull Recipient recipient) { super(context.getString(R.string.reminder_header_invite_title), context.getString(R.string.reminder_header_invite_text, recipient.toShortString())); - setDismissListener(new OnClickListener() { - @Override public void onClick(View v) { - new AsyncTask() { - - @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient, true); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); + setDismissListener(v -> SignalExecutors.BOUNDED.execute(() -> { + DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient.getId(), true); + })); } } diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java index 331ba35f13..4ee1decd78 100644 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java @@ -41,9 +41,9 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.VerifySpan; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.webrtc.CameraState; @@ -57,7 +57,7 @@ import org.whispersystems.libsignal.IdentityKey; * @author Moxie Marlinspike * */ -public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedListener { +public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObserver { @SuppressWarnings("unused") private static final String TAG = WebRtcCallScreen.class.getSimpleName(); @@ -82,8 +82,8 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi private WebRtcAnswerDeclineButton incomingCallButton; - private Recipient recipient; - private boolean minimized; + private LiveRecipient recipient; + private boolean minimized; public WebRtcCallScreen(Context context) { @@ -124,14 +124,18 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi } public void setUntrustedIdentity(Recipient personInfo, IdentityKey untrustedIdentity) { - String name = recipient.toShortString(); + String name = recipient.get().toShortString(); String introduction = String.format(getContext().getString(R.string.WebRtcCallScreen_new_safety_numbers), name, name); SpannableString spannableString = new SpannableString(introduction + " " + getContext().getString(R.string.WebRtcCallScreen_you_may_wish_to_verify_this_contact)); - spannableString.setSpan(new VerifySpan(getContext(), personInfo.getAddress(), untrustedIdentity), + spannableString.setSpan(new VerifySpan(getContext(), personInfo.getId(), untrustedIdentity), introduction.length()+1, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (this.recipient != null) this.recipient.removeForeverObserver(this); + this.recipient = personInfo.live(); + this.recipient.observeForever(this); + setPersonInfo(personInfo); incomingCallButton.stopRingingAnimation(); @@ -294,9 +298,6 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi } private void setPersonInfo(final @NonNull Recipient recipient) { - this.recipient = recipient; - this.recipient.addListener(this); - GlideApp.with(getContext().getApplicationContext()) .load(recipient.getContactPhoto()) .fallback(recipient.getFallbackContactPhoto().asCallCard(getContext())) @@ -307,14 +308,19 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi this.name.setText(recipient.getName()); if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) { - this.phoneNumber.setText(recipient.getAddress().serialize() + " (~" + recipient.getProfileName() + ")"); + this.phoneNumber.setText(recipient.requireAddress().serialize() + " (~" + recipient.getProfileName() + ")"); } else { - this.phoneNumber.setText(recipient.getAddress().serialize()); + this.phoneNumber.setText(recipient.requireAddress().serialize()); } } private void setCard(Recipient recipient, String status) { + if (this.recipient != null) this.recipient.removeForeverObserver(this); + this.recipient = recipient.live(); + this.recipient.observeForever(this); + setPersonInfo(recipient); + this.status.setText(status); this.untrustedIdentityContainer.setVisibility(View.GONE); } @@ -341,17 +347,11 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi } @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> { - if (recipient == WebRtcCallScreen.this.recipient) { - setPersonInfo(recipient); - } - }); + public void onRecipientChanged(@NonNull Recipient recipient) { + setPersonInfo(recipient); } public interface HangupButtonListener { void onClick(); } - - } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java index e7e5027486..fb3f3fecb4 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -29,10 +29,14 @@ import android.provider.ContactsContract.PhoneLookup; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import com.annimon.stream.Stream; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.ArrayList; @@ -72,7 +76,7 @@ public class ContactAccessor { try (Cursor cursor = context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER}, null ,null, null)) { while (cursor != null && cursor.moveToNext()) { if (!TextUtils.isEmpty(cursor.getString(0))) { - results.add(Address.fromExternal(context, cursor.getString(0))); + results.add(Address.fromSerialized(PhoneNumberFormatter.get(context).format(cursor.getString(0)))); } } } @@ -105,7 +109,7 @@ public class ContactAccessor { final ContentResolver resolver = context.getContentResolver(); final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME}; - final List
registeredAddresses = DatabaseFactory.getRecipientDatabase(context).getRegistered(); + final List
registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered()).map(Recipient::resolved).map(Recipient::requireAddress).toList(); final Collection lookupData = new ArrayList<>(registeredAddresses.size()); for (Address registeredAddress : registeredAddresses) { diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java index 64d0c1a497..6b02c0a311 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java @@ -13,13 +13,14 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; -public class ContactSelectionListItem extends LinearLayout implements RecipientModifiedListener { +public class ContactSelectionListItem extends LinearLayout implements RecipientForeverObserver { @SuppressWarnings("unused") private static final String TAG = ContactSelectionListItem.class.getSimpleName(); @@ -31,7 +32,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM private CheckBox checkBox; private String number; - private Recipient recipient; + private LiveRecipient recipient; private GlideRequests glideRequests; public ContactSelectionListItem(Context context) { @@ -60,22 +61,22 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM if (type == ContactsDatabase.NEW_TYPE) { this.recipient = null; - this.contactPhotoImage.setAvatar(glideRequests, Recipient.from(getContext(), Address.UNKNOWN, true), false); + this.contactPhotoImage.setAvatar(glideRequests, Recipient.UNKNOWN, false); } else if (!TextUtils.isEmpty(number)) { - Address address = Address.fromExternal(getContext(), number); - this.recipient = Recipient.from(getContext(), address, true); - this.recipient.addListener(this); + // TODO [greyson] We really don't want to have to do a read like this here + this.recipient = Recipient.external(getContext(), number).live(); + this.recipient.observeForever(this); - if (this.recipient.getName() != null) { - name = this.recipient.getName(); + if (this.recipient.get().getName() != null) { + name = this.recipient.get().getName(); } } this.nameView.setTextColor(color); this.numberView.setTextColor(color); - this.contactPhotoImage.setAvatar(glideRequests, recipient, false); + this.contactPhotoImage.setAvatar(glideRequests, recipient != null ? recipient.get() : null, false); - if (!multiSelect && recipient != null && recipient.isLocalNumber()) { + if (!multiSelect && recipient != null && recipient.get().isLocalNumber()) { name = getContext().getString(R.string.note_to_self); } @@ -91,7 +92,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM public void unbind(GlideRequests glideRequests) { if (recipient != null) { - recipient.removeListener(this); + recipient.removeForeverObserver(this); recipient = null; } } @@ -120,12 +121,8 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM } @Override - public void onModified(final Recipient recipient) { - if (this.recipient == recipient) { - Util.runOnMain(() -> { - contactPhotoImage.setAvatar(glideRequests, recipient, false); - nameView.setText(recipient.toShortString()); - }); - } + public void onRecipientChanged(@NonNull Recipient recipient) { + contactPhotoImage.setAvatar(glideRequests, recipient, false); + nameView.setText(recipient.toShortString()); } } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 33acec6e48..4226b93e9a 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -27,7 +27,6 @@ import androidx.loader.content.CursorLoader; import android.text.TextUtils; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; @@ -36,7 +35,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.NumberUtil; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import java.util.ArrayList; import java.util.List; @@ -170,7 +169,7 @@ public class ContactsCursorLoader extends CursorLoader { ThreadRecord threadRecord; while ((threadRecord = reader.getNext()) != null) { recentConversations.addRow(new Object[] { threadRecord.getRecipient().toShortString(), - threadRecord.getRecipient().getAddress().serialize(), + threadRecord.getRecipient().requireAddress().serialize(), ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactsDatabase.RECENT_TYPE }); @@ -230,7 +229,7 @@ public class ContactsCursorLoader extends CursorLoader { final MatrixCursor matrix = new MatrixCursor(CONTACT_PROJECTION); while (cursor.moveToNext()) { final String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN)); - final Recipient recipient = Recipient.from(getContext(), Address.fromExternal(getContext(), number), false); + final Recipient recipient = Recipient.external(getContext(), number); if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) { matrix.addRow(new Object[]{cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)), diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java index b552f48189..3a5f1d4a1e 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java @@ -40,6 +40,7 @@ import android.util.Pair; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -484,7 +485,7 @@ public class ContactsDatabase { cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null); while (cursor != null && cursor.moveToNext()) { - Address currentAddress = Address.fromExternal(context, cursor.getString(1)); + Address currentAddress = Address.fromSerialized(PhoneNumberFormatter.get(context).format(cursor.getString(1))); long rawContactId = cursor.getLong(0); long contactId = cursor.getLong(3); String supportsVoice = cursor.getString(2); @@ -518,7 +519,7 @@ public class ContactsDatabase { while (numberCursor != null && numberCursor.moveToNext()) { String systemNumber = numberCursor.getString(0); - Address systemAddress = Address.fromExternal(context, systemNumber); + Address systemAddress = Address.fromSerialized(PhoneNumberFormatter.get(context).format(systemNumber)); if (systemAddress.equals(address)) { idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI, diff --git a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java index 2f46eff3dc..c231a39a77 100644 --- a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java +++ b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java @@ -187,7 +187,7 @@ public class RecipientsEditor extends AppCompatMultiAutoCompleteTextView { public static CharSequence contactToToken(Recipient c) { String name = c.getName(); - String number = c.getAddress().serialize(); + String number = c.requireAddress().serialize(); SpannableString s = new SpannableString(RecipientsFormatter.formatNameAndNumber(name, number)); int len = s.length(); @@ -195,7 +195,7 @@ public class RecipientsEditor extends AppCompatMultiAutoCompleteTextView { return s; } - s.setSpan(new Annotation("number", c.getAddress().serialize()), 0, len, + s.setSpan(new Annotation("number", c.requireAddress().serialize()), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return s; diff --git a/src/org/thoughtcrime/securesms/contactshare/ContactRepository.java b/src/org/thoughtcrime/securesms/contactshare/ContactRepository.java index f606d3444a..4d49b32826 100644 --- a/src/org/thoughtcrime/securesms/contactshare/ContactRepository.java +++ b/src/org/thoughtcrime/securesms/contactshare/ContactRepository.java @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; @@ -267,7 +268,7 @@ public class ContactRepository { } for (Phone phoneNumber : phoneNumbers) { - AvatarInfo recipientAvatar = getRecipientAvatarInfo(Address.fromExternal(context, phoneNumber.getNumber())); + AvatarInfo recipientAvatar = getRecipientAvatarInfo(PhoneNumberFormatter.get(context).format(phoneNumber.getNumber())); if (recipientAvatar != null) { return recipientAvatar; } @@ -286,8 +287,8 @@ public class ContactRepository { } @WorkerThread - private @Nullable AvatarInfo getRecipientAvatarInfo(@NonNull Address address) { - Recipient recipient = Recipient.from(context, address, false); + private @Nullable AvatarInfo getRecipientAvatarInfo(String address) { + Recipient recipient = Recipient.external(context, address); ContactPhoto contactPhoto = recipient.getContactPhoto(); if (contactPhoto != null) { diff --git a/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java b/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java index c491ba1574..13c72751bc 100644 --- a/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java +++ b/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java @@ -22,10 +22,12 @@ import org.thoughtcrime.securesms.components.emoji.EmojiStrings; import org.thoughtcrime.securesms.contactshare.Contact.Email; import org.thoughtcrime.securesms.contactshare.Contact.Phone; import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.Util; @@ -112,8 +114,7 @@ public final class ContactUtil { } public static @NonNull String getNormalizedPhoneNumber(@NonNull Context context, @NonNull String number) { - Address address = Address.fromExternal(context, number); - return address.serialize(); + return PhoneNumberFormatter.get(context).format(number); } @MainThread @@ -122,7 +123,7 @@ public final class ContactUtil { CharSequence[] values = new CharSequence[choices.size()]; for (int i = 0; i < values.length; i++) { - values[i] = getPrettyPhoneNumber(choices.get(i).getAddress().toPhoneString(), locale); + values[i] = getPrettyPhoneNumber(choices.get(i).requireAddress().toPhoneString(), locale); } new AlertDialog.Builder(context) @@ -134,8 +135,8 @@ public final class ContactUtil { } } - public static List getRecipients(@NonNull Context context, @NonNull Contact contact) { - return Stream.of(contact.getPhoneNumbers()).map(phone -> Recipient.from(context, Address.fromExternal(context, phone.getNumber()), true)).toList(); + public static List getRecipients(@NonNull Context context, @NonNull Contact contact) { + return Stream.of(contact.getPhoneNumbers()).map(phone -> Recipient.external(context, phone.getNumber())).map(Recipient::getId).toList(); } @WorkerThread diff --git a/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java b/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java index 5a4a261ff1..352e69a6da 100644 --- a/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java @@ -27,13 +27,13 @@ import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; import java.util.Collections; @@ -43,7 +43,7 @@ import java.util.Map; import static org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.*; -public class SharedContactDetailsActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener { +public class SharedContactDetailsActivity extends PassphraseRequiredActionBarActivity { private static final int CODE_ADD_EDIT_CONTACT = 2323; private static final String KEY_CONTACT = "contact"; @@ -64,7 +64,7 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private final Map activeRecipients = new HashMap<>(); + private final Map activeRecipients = new HashMap<>(); public static Intent getIntent(@NonNull Context context, @NonNull Contact contact) { Intent intent = new Intent(context, SharedContactDetailsActivity.class); @@ -97,6 +97,10 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct presentContact(contact); presentActionButtons(ContactUtil.getRecipients(this, contact)); presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getDataUri() : null); + + for (LiveRecipient recipient : activeRecipients.values()) { + recipient.observe(this, r -> presentActionButtons(Collections.singletonList(r.getId()))); + } } @Override @@ -144,11 +148,6 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct glideRequests = GlideApp.with(this); } - @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> presentActionButtons(Collections.singletonList(recipient))); - } - @SuppressLint("StaticFieldLeak") private void presentContact(@Nullable Contact contact) { this.contact = contact; @@ -193,21 +192,19 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct } } - private void presentActionButtons(@NonNull List recipients) { - for (Recipient recipient : recipients) { - activeRecipients.put(recipient.getAddress().serialize(), recipient); + private void presentActionButtons(@NonNull List recipients) { + for (RecipientId recipientId : recipients) { + activeRecipients.put(recipientId, Recipient.live(recipientId)); } List pushUsers = new ArrayList<>(recipients.size()); List systemUsers = new ArrayList<>(recipients.size()); - for (Recipient recipient : activeRecipients.values()) { - recipient.addListener(this); - - if (recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { - pushUsers.add(recipient); - } else if (recipient.isSystemContact()) { - systemUsers.add(recipient); + for (LiveRecipient recipient : activeRecipients.values()) { + if (recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { + pushUsers.add(recipient.get()); + } else if (recipient.get().isSystemContact()) { + systemUsers.add(recipient.get()); } } @@ -230,7 +227,7 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct inviteButtonView.setOnClickListener(v -> { ContactUtil.selectRecipientThroughDialog(this, systemUsers, dynamicLanguage.getCurrentLocale(), recipient -> { - CommunicationActions.composeSmsThroughDefaultApp(this, recipient.getAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); + CommunicationActions.composeSmsThroughDefaultApp(this, recipient.requireAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); }); }); } else { diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 59e28fb8a4..7713c3982d 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -128,7 +128,6 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -184,10 +183,10 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.GroupShareProfileView; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientExporter; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.search.model.MessageResult; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; @@ -249,7 +248,6 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; public class ConversationActivity extends PassphraseRequiredActionBarActivity implements ConversationFragment.ConversationFragmentListener, AttachmentManager.AttachmentListener, - RecipientModifiedListener, OnKeyboardShownListener, InputPanel.Listener, InputPanel.MediaListener, @@ -259,7 +257,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity { private static final String TAG = ConversationActivity.class.getSimpleName(); - public static final String ADDRESS_EXTRA = "address"; + public static final String RECIPIENT_EXTRA = "recipient_id"; public static final String THREAD_ID_EXTRA = "thread_id"; public static final String IS_ARCHIVED_EXTRA = "is_archived"; public static final String TEXT_EXTRA = "draft_text"; @@ -316,14 +314,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ConversationSearchViewModel searchViewModel; private ConversationStickerViewModel stickerViewModel; - private Recipient recipient; - private long threadId; - private int distributionType; - private boolean archived; - private boolean isSecureText; - private boolean isDefaultSms = true; - private boolean isMmsEnabled = true; - private boolean isSecurityInitialized = false; + private LiveRecipient recipient; + private long threadId; + private int distributionType; + private boolean archived; + private boolean isSecureText; + private boolean isDefaultSms = true; + private boolean isMmsEnabled = true; + private boolean isSecurityInitialized = false; private final IdentityRecordList identityRecords = new IdentityRecordList(); private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme(); @@ -428,10 +426,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity initializeIdentityRecords(); composeText.setTransport(sendButton.getSelectedTransport()); - titleView.setTitle(glideRequests, recipient); - setActionBarColor(recipient.getColor()); - setBlockedUserState(recipient, isSecureText, isDefaultSms); - setGroupShareProfileReminder(recipient); + Recipient recipientSnapshot = recipient.get(); + + titleView.setTitle(glideRequests, recipientSnapshot); + setActionBarColor(recipientSnapshot.getColor()); + setBlockedUserState(recipientSnapshot, isSecureText, isDefaultSms); + setGroupShareProfileReminder(recipientSnapshot); calculateCharactersRemaining(); MessageNotifier.setVisibleThread(threadId); @@ -473,7 +473,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected void onDestroy() { saveDraft(); - if (recipient != null) recipient.removeListener(this); if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver); super.onDestroy(); } @@ -508,11 +507,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity sendSharedContact(data.getParcelableArrayListExtra(ContactShareEditActivity.KEY_CONTACTS)); break; case GROUP_EDIT: - recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true); - recipient.addListener(this); - titleView.setTitle(glideRequests, recipient); - NotificationChannels.updateContactChannelName(this, recipient); - setBlockedUserState(recipient, isSecureText, isDefaultSms); + Recipient recipientSnapshot = recipient.get(); + + onRecipientChanged(recipientSnapshot); + titleView.setTitle(glideRequests, recipientSnapshot); + NotificationChannels.updateContactChannelName(this, recipientSnapshot); + setBlockedUserState(recipientSnapshot, isSecureText, isDefaultSms); supportInvalidateOptionsMenu(); break; case TAKE_PHOTO: @@ -521,8 +521,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } break; case ADD_CONTACT: - recipient = Recipient.from(this, recipient.getAddress(), true); - recipient.addListener(this); + onRecipientChanged(recipient.get()); fragment.reloadList(); break; case PICK_LOCATION: @@ -539,7 +538,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity initializeSecurity(isSecureText, isDefaultSms); break; case MEDIA_SENDER: - long expiresIn = recipient.getExpireMessages() * 1000L; + long expiresIn = recipient.get().getExpireMessages() * 1000L; int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); boolean initiating = threadId == -1; TransportOption transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT); @@ -617,14 +616,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity menu.clear(); if (isSecureText) { - if (recipient.getExpireMessages() > 0) { + if (recipient.get().getExpireMessages() > 0) { inflater.inflate(R.menu.conversation_expiring_on, menu); final MenuItem item = menu.findItem(R.id.menu_expiring_messages); final View actionView = MenuItemCompat.getActionView(item); final TextView badgeView = actionView.findViewById(R.id.expiration_badge); - badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipient.getExpireMessages())); + badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipient.get().getExpireMessages())); actionView.setOnClickListener(v -> onOptionsItemSelected(item)); } else { inflater.inflate(R.menu.conversation_expiring_off, menu); @@ -657,14 +656,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inflater.inflate(R.menu.conversation_insecure, menu); } - if (recipient != null && recipient.isMuted()) inflater.inflate(R.menu.conversation_muted, menu); - else inflater.inflate(R.menu.conversation_unmuted, menu); + if (recipient != null && recipient.get().isMuted()) inflater.inflate(R.menu.conversation_muted, menu); + else inflater.inflate(R.menu.conversation_unmuted, menu); if (isSingleConversation() && getRecipient().getContactUri() == null) { inflater.inflate(R.menu.conversation_add_to_contacts, menu); } - if (recipient != null && recipient.isLocalNumber()) { + if (recipient != null && recipient.get().isLocalNumber()) { if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); else menu.findItem(R.id.menu_call_insecure).setVisible(false); @@ -771,7 +770,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(ReminderUpdateEvent event) { - updateReminders(recipient.hasSeenInviteReminder()); + updateReminders(recipient.get().hasSeenInviteReminder()); } @Override @@ -794,11 +793,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } //noinspection CodeBlock2Expr - ExpirationDialog.show(this, recipient.getExpireMessages(), expirationTime -> { + ExpirationDialog.show(this, recipient.get().getExpireMessages(), expirationTime -> { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); + DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient.getId(), expirationTime); OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipient(), System.currentTimeMillis(), expirationTime * 1000L); MessageSender.send(ConversationActivity.this, outgoingMessage, threadId, false, null); @@ -816,13 +815,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleMuteNotifications() { MuteDialog.show(this, until -> { - recipient.setMuted(until); - new AsyncTask() { @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, until); + .setMuted(recipient.getId(), until); return null; } @@ -832,7 +829,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleConversationSettings() { Intent intent = new Intent(ConversationActivity.this, RecipientPreferenceActivity.class); - intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId()); intent.putExtra(RecipientPreferenceActivity.CAN_HAVE_SAFETY_NUMBER_EXTRA, isSecureText && !isSelfConversation()); @@ -840,13 +837,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleUnmuteNotifications() { - recipient.setMuted(0); - new AsyncTask() { @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, 0); + .setMuted(recipient.getId(), 0); return null; } @@ -857,7 +852,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity int titleRes = R.string.ConversationActivity_unblock_this_contact_question; int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; - if (recipient.isGroupRecipient()) { + if (recipient.get().isGroup()) { titleRes = R.string.ConversationActivity_unblock_this_group_question; bodyRes = R.string.ConversationActivity_unblock_this_group_description; } @@ -872,7 +867,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, false); + .setBlocked(recipient.getId(), false); ApplicationContext.getInstance(ConversationActivity.this) .getJobManager() @@ -904,7 +899,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeText.appendInvite(inviteText); } else { Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.parse("smsto:" + recipient.getAddress().serialize())); + intent.setData(Uri.parse("smsto:" + recipient.get().requireAddress().serialize())); intent.putExtra("sms_body", inviteText); intent.putExtra(Intent.EXTRA_TEXT, inviteText); startActivity(intent); @@ -943,12 +938,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleViewMedia() { Intent intent = new Intent(this, MediaOverviewActivity.class); - intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(MediaOverviewActivity.RECIPIENT_EXTRA, recipient.getId()); startActivity(intent); } private void handleAddShortcut() { - Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.getAddress()); + Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.get().requireAddress()); new AsyncTask() { @@ -957,9 +952,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Context context = getApplicationContext(); IconCompat icon = null; - if (recipient.getContactPhoto() != null) { + if (recipient.get().getContactPhoto() != null) { try { - Bitmap bitmap = BitmapFactory.decodeStream(recipient.getContactPhoto().openInputStream(context)); + Bitmap bitmap = BitmapFactory.decodeStream(recipient.get().getContactPhoto().openInputStream(context)); bitmap = BitmapUtil.createScaledBitmap(bitmap, 300, 300); icon = IconCompat.createWithAdaptiveBitmap(bitmap); } catch (IOException e) { @@ -968,8 +963,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } if (icon == null) { - icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut - : R.mipmap.ic_person_shortcut); + icon = IconCompat.createWithResource(context, recipient.get().isGroup() ? R.mipmap.ic_group_shortcut + : R.mipmap.ic_person_shortcut); } return icon; @@ -978,14 +973,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected void onPostExecute(IconCompat icon) { Context context = getApplicationContext(); - String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.toShortString()); + String name = Optional.fromNullable(recipient.get().getName()) + .or(Optional.fromNullable(recipient.get().getProfileName())) + .or(recipient.get().toShortString()); - ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis()) + ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.get().requireAddress().serialize() + '-' + System.currentTimeMillis()) .setShortLabel(name) .setIcon(icon) - .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) + .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getId())) .build(); if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { @@ -1020,9 +1015,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity MessageSender.send(this, leaveMessage.get(), threadId, false, null); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); - String groupId = groupRecipient.getAddress().toGroupString(); + String groupId = groupRecipient.requireAddress().toGroupString(); groupDatabase.setActive(groupId, false); - groupDatabase.remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this))); + groupDatabase.remove(groupId, Recipient.self().getId()); initializeEnabledCheck(); } else { @@ -1036,7 +1031,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleEditPushGroup() { Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class); - intent.putExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA, recipient.get().requireAddress()); startActivityForResult(intent, GROUP_EDIT); } @@ -1080,7 +1075,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { try { Intent dialIntent = new Intent(Intent.ACTION_DIAL, - Uri.parse("tel:" + recipient.getAddress().serialize())); + Uri.parse("tel:" + recipient.requireAddress().serialize())); startActivity(dialIntent); } catch (ActivityNotFoundException anfe) { Log.w(TAG, anfe); @@ -1096,20 +1091,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleAddToContacts() { - if (recipient.getAddress().isGroup()) return; + if (recipient.get().requireAddress().isGroup()) return; try { - startActivityForResult(RecipientExporter.export(recipient).asAddContactIntent(), ADD_CONTACT); + startActivityForResult(RecipientExporter.export(recipient.get()).asAddContactIntent(), ADD_CONTACT); } catch (ActivityNotFoundException e) { Log.w(TAG, e); } } private boolean handleDisplayQuickContact() { - if (recipient.getAddress().isGroup()) return false; + if (recipient.get().requireAddress().isGroup()) return false; - if (recipient.getContactUri() != null) { - ContactsContract.QuickContact.showQuickContact(ConversationActivity.this, titleView, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); + if (recipient.get().getContactUri() != null) { + ContactsContract.QuickContact.showQuickContact(ConversationActivity.this, titleView, recipient.get().getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); } else { handleAddToContacts(); } @@ -1193,14 +1188,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity this.isDefaultSms = isDefaultSms; this.isSecurityInitialized = true; - boolean isMediaMessage = recipient.isMmsGroupRecipient() || attachmentManager.isAttachmentPresent(); + boolean isMediaMessage = recipient.get().isMmsGroup() || attachmentManager.isAttachmentPresent(); sendButton.resetAvailableTransports(isMediaMessage); if (!isSecureText && !isPushGroupConversation()) sendButton.disableTransport(Type.TEXTSECURE); - if (recipient.isPushGroupRecipient()) sendButton.disableTransport(Type.SMS); + if (recipient.get().isPushGroup()) sendButton.disableTransport(Type.SMS); - if (!recipient.isPushGroupRecipient() && recipient.isForceSmsSelection()) { + if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection()) { sendButton.setDefaultTransport(Type.SMS); } else { if (isSecureText || isPushGroupConversation()) sendButton.setDefaultTransport(Type.TEXTSECURE); @@ -1209,7 +1204,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity calculateCharactersRemaining(); supportInvalidateOptionsMenu(); - setBlockedUserState(recipient, isSecureText, isDefaultSms); + setBlockedUserState(recipient.get(), isSecureText, isDefaultSms); } ///// Initializers @@ -1229,7 +1224,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } if (!Util.isEmpty(mediaList)) { - Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient, draftText, sendButton.getSelectedTransport()); + Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient.get(), draftText, sendButton.getSelectedTransport()); startActivityForResult(sendIntent, MEDIA_SENDER); return new SettableFuture<>(false); } @@ -1344,16 +1339,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected boolean[] doInBackground(Recipient... params) { Context context = ConversationActivity.this; - Recipient recipient = params[0]; + Recipient recipient = params[0].resolve(); Log.i(TAG, "Resolving registered state..."); RegisteredState registeredState; - if (recipient.isPushGroupRecipient()) { + if (recipient.isPushGroup()) { Log.i(TAG, "Push group recipient..."); registeredState = RegisteredState.REGISTERED; - } else if (recipient.isResolving()) { - Log.i(TAG, "Talking to DB directly."); - registeredState = DatabaseFactory.getRecipientDatabase(ConversationActivity.this).isRegistered(recipient.getAddress()); } else { Log.i(TAG, "Checking through resolved recipient"); registeredState = recipient.resolve().getRegistered(); @@ -1364,7 +1356,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (registeredState == RegisteredState.UNKNOWN) { try { - Log.i(TAG, "Refreshing directory for user: " + recipient.getAddress().serialize()); + Log.i(TAG, "Refreshing directory for user: " + recipient.requireAddress().serialize()); registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient); } catch (IOException e) { Log.w(TAG, e); @@ -1385,15 +1377,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity future.set(true); onSecurityUpdated(); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get()); return future; } private void onSecurityUpdated() { Log.i(TAG, "onSecurityUpdated()"); - updateReminders(recipient.hasSeenInviteReminder()); - updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); + updateReminders(recipient.get().hasSeenInviteReminder()); + updateDefaultSubscriptionId(recipient.get().getDefaultSubscriptionId()); } protected void updateReminders(boolean seenInvite) { @@ -1410,9 +1402,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity TextSecurePreferences.isShowInviteReminders(this) && !isSecureText && !seenInvite && - !recipient.isGroupRecipient()) + !recipient.get().isGroup()) { - InviteReminder reminder = new InviteReminder(this, recipient); + InviteReminder reminder = new InviteReminder(this, recipient.get()); reminder.setOkListener(v -> { handleInviteLink(); reminderView.get().requestDismiss(); @@ -1452,16 +1444,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity IdentityRecordList identityRecordList = new IdentityRecordList(); List recipients = new LinkedList<>(); - if (params[0].isGroupRecipient()) { + if (params[0].isGroup()) { recipients.addAll(DatabaseFactory.getGroupDatabase(ConversationActivity.this) - .getGroupMembers(params[0].getAddress().toGroupString(), false)); + .getGroupMembers(params[0].requireAddress().toGroupString(), false)); } else { recipients.add(params[0]); } for (Recipient recipient : recipients) { - Log.i(TAG, "Loading identity for: " + recipient.getAddress()); - identityRecordList.add(identityDatabase.getIdentity(recipient.getAddress())); + Log.i(TAG, "Loading identity for: " + recipient.requireAddress()); + identityRecordList.add(identityDatabase.getIdentity(recipient.getId())); } String message = null; @@ -1493,7 +1485,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity future.set(true); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get()); return future; } @@ -1585,15 +1577,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeResources() { - if (recipient != null) recipient.removeListener(this); - - recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true); + recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA)); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false); distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT); glideRequests = GlideApp.with(this); - recipient.addListener(this); + recipient.observe(this, this::onRecipientChanged); } @@ -1628,7 +1618,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (!result.getResults().isEmpty()) { MessageResult messageResult = result.getResults().get(result.getPosition()); - fragment.jumpToMessage(messageResult.messageRecipient.getAddress(), messageResult.receivedTimestampMs, searchViewModel::onMissingResult); + fragment.jumpToMessage(messageResult.messageRecipient.getId(), messageResult.receivedTimestampMs, searchViewModel::onMissingResult); } searchNav.setData(result.getPosition(), result.getResults().size()); @@ -1699,27 +1689,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity ApplicationContext.getInstance(this) .getJobManager() - .add(new RetrieveProfileJob(recipient)); + .add(new RetrieveProfileJob(recipient.get())); } - @Override - public void onModified(final Recipient recipient) { - Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")"); - Util.runOnMain(() -> { - Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered()); - titleView.setTitle(glideRequests, recipient); - titleView.setVerified(identityRecords.isVerified()); - setBlockedUserState(recipient, isSecureText, isDefaultSms); - setActionBarColor(recipient.getColor()); - setGroupShareProfileReminder(recipient); - updateReminders(recipient.hasSeenInviteReminder()); - updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); - initializeSecurity(isSecureText, isDefaultSms); + private void onRecipientChanged(@NonNull Recipient recipient) { + Log.i(TAG, "onModified(" + recipient.requireAddress().serialize() + ") " + recipient.getRegistered()); + titleView.setTitle(glideRequests, recipient); + titleView.setVerified(identityRecords.isVerified()); + setBlockedUserState(recipient, isSecureText, isDefaultSms); + setActionBarColor(recipient.getColor()); + setGroupShareProfileReminder(recipient); + updateReminders(recipient.hasSeenInviteReminder()); + updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); + initializeSecurity(isSecureText, isDefaultSms); - if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) { - invalidateOptionsMenu(); - } - }); + if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) { + invalidateOptionsMenu(); + } } @Subscribe(threadMode = ThreadMode.MAIN) @@ -1760,7 +1746,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "Selected: " + type); switch (type) { case AttachmentTypeSelector.ADD_GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()); break; + AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport()); break; case AttachmentTypeSelector.ADD_DOCUMENT: AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; case AttachmentTypeSelector.ADD_SOUND: @@ -1790,7 +1776,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return new SettableFuture<>(false); } else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) { Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, Optional.absent(), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER); + startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER); return new SettableFuture<>(false); } else { return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height); @@ -1812,7 +1798,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void sendSharedContact(List contacts) { int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); - long expiresIn = recipient.getExpireMessages() * 1000L; + long expiresIn = recipient.get().getExpireMessages() * 1000L; boolean initiating = threadId == -1; sendMediaMessage(isSmsForced(), "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, false, subscriptionId, initiating, false); @@ -1933,7 +1919,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void setGroupShareProfileReminder(@NonNull Recipient recipient) { - if (recipient.isPushGroupRecipient() && !recipient.isProfileSharing()) { + if (recipient.isPushGroup() && !recipient.isProfileSharing()) { groupShareProfileView.get().setRecipient(recipient); groupShareProfileView.get().setVisibility(View.VISIBLE); } else if (groupShareProfileView.resolved()) { @@ -1979,30 +1965,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private boolean isSingleConversation() { - return getRecipient() != null && !getRecipient().isGroupRecipient(); + return getRecipient() != null && !getRecipient().isGroup(); } private boolean isActiveGroup() { if (!isGroupConversation()) return false; - Optional record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()); + Optional record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getId()); return record.isPresent() && record.get().isActive(); } @SuppressWarnings("SimplifiableIfStatement") private boolean isSelfConversation() { if (!TextSecurePreferences.isPushRegistered(this)) return false; - if (recipient.isGroupRecipient()) return false; + if (recipient.get().isGroup()) return false; - return Util.isOwnNumber(this, recipient.getAddress()); + return Util.isOwnNumber(this, recipient.get().requireAddress()); } private boolean isGroupConversation() { - return getRecipient() != null && getRecipient().isGroupRecipient(); + return getRecipient() != null && getRecipient().isGroup(); } private boolean isPushGroupConversation() { - return getRecipient() != null && getRecipient().isPushGroupRecipient(); + return getRecipient() != null && getRecipient().isPushGroup(); } private boolean isSmsForced() { @@ -2010,7 +1996,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } protected Recipient getRecipient() { - return this.recipient; + return this.recipient.get(); } protected long getThreadId() { @@ -2090,7 +2076,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity fragment.setLastSeen(0); if (refreshFragment) { - fragment.reload(recipient, threadId); + fragment.reload(recipient.get(), threadId); MessageNotifier.setVisibleThread(threadId); } @@ -2121,8 +2107,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean initiating = threadId == -1; boolean needsSplit = !transport.isSms() && message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize; boolean isMediaMessage = attachmentManager.isAttachmentPresent() || - recipient.isGroupRecipient() || - recipient.getAddress().isEmail() || + recipient.isGroup() || + recipient.requireAddress().isEmail() || inputPanel.getQuote().isPresent() || linkPreviewViewModel.hasLinkPreview() || needsSplit; @@ -2130,7 +2116,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "isManual Selection: " + sendButton.isManualSelection()); Log.i(TAG, "forceSms: " + forceSms); - if ((recipient.isMmsGroupRecipient() || recipient.getAddress().isEmail()) && !isMmsEnabled) { + if ((recipient.isMmsGroup() || recipient.requireAddress().isEmail()) && !isMmsEnabled) { handleManualMmsRequired(); } else if (!forceSms && identityRecords.isUnverified()) { handleUnverifiedRecipients(); @@ -2186,7 +2172,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } - OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews); + OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient.get(), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews); final SettableFuture future = new SettableFuture<>(); final Context context = getApplicationContext(); @@ -2217,7 +2203,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Long doInBackground(Void... param) { if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true); } return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); @@ -2250,10 +2236,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity OutgoingTextMessage message; if (isSecureText && !forceSms) { - message = new OutgoingEncryptedMessage(recipient, messageBody, expiresIn); + message = new OutgoingEncryptedMessage(recipient.get(), messageBody, expiresIn); ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); } else { - message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId); + message = new OutgoingTextMessage(recipient.get(), messageBody, expiresIn, subscriptionId); } Permissions.with(this) @@ -2268,7 +2254,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Long doInBackground(OutgoingTextMessage... messages) { if (initiatingConversation) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true); } return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); @@ -2331,10 +2317,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Void... params) { RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(ConversationActivity.this); - recipientDatabase.setDefaultSubscriptionId(recipient, transportOption.getSimSubscriptionId().or(-1)); + recipientDatabase.setDefaultSubscriptionId(recipient.getId(), transportOption.getSimSubscriptionId().or(-1)); - if (!recipient.isPushGroupRecipient()) { - recipientDatabase.setForceSmsSelection(recipient, recipient.getRegistered() == RegisteredState.REGISTERED && transportOption.isSms()); + if (!recipient.resolve().isPushGroup()) { + recipientDatabase.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && transportOption.isSms()); } return null; @@ -2381,7 +2367,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public void onSuccess(final @NonNull Pair result) { boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); - long expiresIn = recipient.getExpireMessages() * 1000L; + long expiresIn = recipient.get().getExpireMessages() * 1000L; boolean initiating = threadId == -1; AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true); SlideDeck slideDeck = new SlideDeck(); @@ -2502,12 +2488,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) { if (sendButton.getSelectedTransport().isSms()) { Media media = new Media(uri, MediaUtil.IMAGE_WEBP, System.currentTimeMillis(), StickerSlide.WIDTH, StickerSlide.HEIGHT, size, Optional.absent(), Optional.absent()); - Intent intent = MediaSendActivity.buildEditorIntent(this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()); + Intent intent = MediaSendActivity.buildEditorIntent(this, Collections.singletonList(media), recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport()); startActivityForResult(intent, MEDIA_SENDER); return; } - long expiresIn = recipient.getExpireMessages() * 1000L; + long expiresIn = recipient.get().getExpireMessages() * 1000L; int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); boolean initiating = threadId == -1; TransportOption transport = sendButton.getSelectedTransport(); @@ -2538,7 +2524,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { linkPreviewViewModel.onUserCancel(); Media media = new Media(uri, mimeType, dateTaken, width, height, size, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER); + startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER); } } @@ -2552,7 +2538,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) .onAllGranted(() -> { composeText.clearFocus(); - startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient, sendButton.getSelectedTransport()), MEDIA_SENDER); + startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient.get(), sendButton.getSelectedTransport()), MEDIA_SENDER); overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); }) .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) @@ -2662,7 +2648,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Recipient author; if (messageRecord.isOutgoing()) { - author = Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true); + author = Recipient.self(); } else { author = messageRecord.getIndividualRecipient(); } @@ -2740,7 +2726,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Void... params) { synchronized (SESSION_LOCK) { for (IdentityRecord identityRecord : unverifiedIdentities) { - identityDatabase.setVerified(identityRecord.getAddress(), + identityDatabase.setVerified(identityRecord.getRecipientId(), identityRecord.getIdentityKey(), VerifiedStatus.DEFAULT); } @@ -2763,7 +2749,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "onClicked: " + unverifiedIdentities.size()); if (unverifiedIdentities.size() == 1) { Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class); - intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, unverifiedIdentities.get(0).getAddress()); + intent.putExtra(VerifyIdentityActivity.RECIPIENT_EXTRA, unverifiedIdentities.get(0).getRecipientId()); intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(0).getIdentityKey())); intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false); @@ -2772,7 +2758,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String[] unverifiedNames = new String[unverifiedIdentities.size()]; for (int i=0;i { Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class); - intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, unverifiedIdentities.get(which).getAddress()); + intent.putExtra(VerifyIdentityActivity.RECIPIENT_EXTRA, unverifiedIdentities.get(which).getRecipientId()); intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(which).getIdentityKey())); intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false); @@ -2803,7 +2789,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected MessageRecord doInBackground(Void... voids) { - QuoteId quoteId = QuoteId.deserialize(serialized); + QuoteId quoteId = QuoteId.deserialize(ConversationActivity.this, serialized); if (quoteId != null) { return DatabaseFactory.getMmsSmsDatabase(getApplicationContext()).getMessageFor(quoteId.getId(), quoteId.getAuthor()); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 1b4d10f488..50afa0a8d3 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -69,7 +69,6 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity; import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -90,7 +89,9 @@ import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.profiles.UnknownSenderView; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity; import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.sms.MessageSender; @@ -134,7 +135,7 @@ public class ConversationFragment extends Fragment private ConversationFragmentListener listener; - private Recipient recipient; + private LiveRecipient recipient; private long threadId; private long lastSeen; private int startingPosition; @@ -253,12 +254,12 @@ public class ConversationFragment extends Fragment } private void initializeResources() { - this.recipient = Recipient.from(getActivity(), getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true); + this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA)); this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); this.firstLoad = true; - this.unknownSenderView = new UnknownSenderView(getActivity(), recipient, threadId); + this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId); OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); list.addOnScrollListener(scrollListener); @@ -266,7 +267,7 @@ public class ConversationFragment extends Fragment private void initializeListAdapter() { if (this.recipient != null && this.threadId != -1) { - ConversationAdapter adapter = new ConversationAdapter(getActivity(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient); + ConversationAdapter adapter = new ConversationAdapter(requireContext(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient.get()); list.setAdapter(adapter); list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); @@ -302,7 +303,7 @@ public class ConversationFragment extends Fragment replacedByIncomingMessage = false; } - typingView.setTypists(GlideApp.with(ConversationFragment.this), recipients, recipient.isGroupRecipient()); + typingView.setTypists(GlideApp.with(ConversationFragment.this), recipients, recipient.get().isGroup()); ConversationAdapter adapter = getListAdapter(); @@ -417,7 +418,7 @@ public class ConversationFragment extends Fragment } public void reload(Recipient recipient, long threadId) { - this.recipient = recipient; + this.recipient = recipient.live(); if (this.threadId != threadId) { this.threadId = threadId; @@ -521,8 +522,8 @@ public class ConversationFragment extends Fragment intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId()); intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, recipient.isGroupRecipient() && message.isPush()); + intent.putExtra(MessageDetailsActivity.RECIPIENT_EXTRA, recipient.getId()); + intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, recipient.get().isGroup() && message.isPush()); startActivity(intent); } @@ -663,7 +664,7 @@ public class ConversationFragment extends Fragment setLastSeen(loader.getLastSeen()); } - if (!loader.hasSent() && !recipient.isSystemContact() && !recipient.isGroupRecipient() && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { + if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { adapter.setHeaderView(unknownSenderView); } else { clearHeaderIfNotTyping(adapter); @@ -789,7 +790,7 @@ public class ConversationFragment extends Fragment } } - public void jumpToMessage(@NonNull Address author, long timestamp, @Nullable Runnable onMessageNotFound) { + public void jumpToMessage(@NonNull RecipientId author, long timestamp, @Nullable Runnable onMessageNotFound) { SimpleTask.run(getLifecycle(), () -> { return DatabaseFactory.getMmsSmsDatabase(getContext()) .getMessagePositionInConversation(threadId, timestamp, author); @@ -951,9 +952,9 @@ public class ConversationFragment extends Fragment } @Override - public void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms) { + public void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms) { if (getContext() != null && getActivity() != null) { - startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms)); + startActivity(LongMessageActivity.getIntent(getContext(), conversationRecipientId, messageId, isMms)); } } @@ -990,7 +991,7 @@ public class ConversationFragment extends Fragment ApplicationContext.getInstance(requireContext()) .getJobManager() - .add(new MultiDeviceViewOnceOpenJob(new MessagingDatabase.SyncMessageId(messageRecord.getIndividualRecipient().getAddress(), messageRecord.getDateSent()))); + .add(new MultiDeviceViewOnceOpenJob(new MessagingDatabase.SyncMessageId(messageRecord.getIndividualRecipient().getId(), messageRecord.getDateSent()))); return tempUri; } catch (IOException e) { @@ -1046,7 +1047,7 @@ public class ConversationFragment extends Fragment if (getContext() == null) return; ContactUtil.selectRecipientThroughDialog(getContext(), choices, locale, recipient -> { - CommunicationActions.composeSmsThroughDefaultApp(getContext(), recipient.getAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); + CommunicationActions.composeSmsThroughDefaultApp(getContext(), recipient.requireAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); }); } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java index 0cb49fe063..5ed32ff8f8 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -68,6 +68,8 @@ import org.thoughtcrime.securesms.components.DocumentView; import org.thoughtcrime.securesms.components.LinkPreviewView; import org.thoughtcrime.securesms.components.Outliner; import org.thoughtcrime.securesms.components.QuoteView; +import org.thoughtcrime.securesms.recipients.LiveRecipient; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.revealable.ViewOnceMessageView; import org.thoughtcrime.securesms.components.SharedContactView; import org.thoughtcrime.securesms.components.StickerView; @@ -98,7 +100,6 @@ import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.TextSlide; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.stickers.StickerUrl; import org.thoughtcrime.securesms.util.DateUtils; @@ -108,7 +109,6 @@ import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.thoughtcrime.securesms.util.SearchUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ThemeUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.views.Stub; import org.whispersystems.libsignal.util.guava.Optional; @@ -127,8 +127,8 @@ import java.util.Set; * */ -public class ConversationItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem +public class ConversationItem extends LinearLayout implements BindableConversationItem, + RecipientForeverObserver { private static final String TAG = ConversationItem.class.getSimpleName(); @@ -138,7 +138,7 @@ public class ConversationItem extends LinearLayout private MessageRecord messageRecord; private Locale locale; private boolean groupThread; - private Recipient recipient; + private LiveRecipient recipient; private GlideRequests glideRequests; protected ViewGroup bodyBubble; @@ -156,7 +156,7 @@ public class ConversationItem extends LinearLayout private @NonNull Set batchSelected = new HashSet<>(); private @NonNull Outliner outliner = new Outliner(); - private Recipient conversationRecipient; + private LiveRecipient conversationRecipient; private Stub mediaThumbnailStub; private Stub audioViewStub; private Stub documentViewStub; @@ -238,16 +238,19 @@ public class ConversationItem extends LinearLayout @Nullable String searchQuery, boolean pulseHighlight) { + if (this.recipient != null) this.recipient.removeForeverObserver(this); + if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this); + this.messageRecord = messageRecord; this.locale = locale; this.glideRequests = glideRequests; this.batchSelected = batchSelected; - this.conversationRecipient = conversationRecipient; - this.groupThread = conversationRecipient.isGroupRecipient(); - this.recipient = messageRecord.getIndividualRecipient(); + this.conversationRecipient = conversationRecipient.live(); + this.groupThread = conversationRecipient.isGroup(); + this.recipient = messageRecord.getIndividualRecipient().live(); - this.recipient.addListener(this); - this.conversationRecipient.addListener(this); + this.recipient.observeForever(this); + this.conversationRecipient.observeForever(this); setGutterSizes(messageRecord, groupThread); setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); @@ -256,8 +259,8 @@ public class ConversationItem extends LinearLayout setBodyText(messageRecord, searchQuery); setBubbleState(messageRecord); setStatusIcons(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); + setContactPhoto(recipient.get()); + setGroupMessageStatus(messageRecord, recipient.get()); setGroupAuthorColor(messageRecord); setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); @@ -320,6 +323,14 @@ public class ConversationItem extends LinearLayout } } + @Override + public void onRecipientChanged(@NonNull Recipient modified) { + setBubbleState(messageRecord); + setContactPhoto(recipient.get()); + setGroupMessageStatus(messageRecord, recipient.get()); + setAudioViewTint(messageRecord, conversationRecipient.get()); + } + private int getAvailableMessageBubbleWidth(@NonNull View forView) { int availableWidth; if (hasAudio(messageRecord)) { @@ -346,7 +357,7 @@ public class ConversationItem extends LinearLayout @Override public void unbind() { if (recipient != null) { - recipient.removeListener(this); + recipient.removeForeverObserver(this);; } } @@ -372,7 +383,7 @@ public class ConversationItem extends LinearLayout } if (audioViewStub.resolved()) { - setAudioViewTint(messageRecord, this.conversationRecipient); + setAudioViewTint(messageRecord, this.conversationRecipient.get()); } } @@ -820,7 +831,7 @@ public class ConversationItem extends LinearLayout if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) { Quote quote = ((MediaMmsMessageRecord)current).getQuote(); //noinspection ConstantConditions - quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quote.getText(), quote.isOriginalMissing(), quote.getAttachment()); + quoteView.setQuote(glideRequests, quote.getId(), Recipient.live(quote.getAuthor()).get(), quote.getText(), quote.isOriginalMissing(), quote.getAttachment()); quoteView.setVisibility(View.VISIBLE); quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; @@ -944,7 +955,7 @@ public class ConversationItem extends LinearLayout if (isGroupThread && !current.isOutgoing()) { contactPhotoHolder.setVisibility(VISIBLE); - if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()) || + if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().requireAddress().equals(previous.get().getRecipient().requireAddress()) || !DateUtils.isSameDay(previous.get().getTimestamp(), current.getTimestamp())) { groupSenderHolder.setVisibility(VISIBLE); @@ -952,7 +963,7 @@ public class ConversationItem extends LinearLayout groupSenderHolder.setVisibility(GONE); } - if (!next.isPresent() || next.get().isUpdate() || !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress())) { + if (!next.isPresent() || next.get().isUpdate() || !current.getRecipient().requireAddress().equals(next.get().getRecipient().requireAddress())) { contactPhoto.setVisibility(VISIBLE); } else { contactPhoto.setVisibility(GONE); @@ -1012,7 +1023,7 @@ public class ConversationItem extends LinearLayout private boolean isStartOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional previous, boolean isGroupThread) { if (isGroupThread) { return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()); + !current.getRecipient().requireAddress().equals(previous.get().getRecipient().requireAddress()); } else { return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || current.isOutgoing() != previous.get().isOutgoing(); @@ -1022,7 +1033,7 @@ public class ConversationItem extends LinearLayout private boolean isEndOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional next, boolean isGroupThread) { if (isGroupThread) { return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress()); + !current.getRecipient().requireAddress().equals(next.get().getRecipient().requireAddress()); } else { return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || current.isOutgoing() != next.get().isOutgoing(); @@ -1074,7 +1085,7 @@ public class ConversationItem extends LinearLayout if (slide != null && slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); + action = () -> eventListener.onMoreTextClicked(conversationRecipient.getId(), messageRecord.getId(), messageRecord.isMms()); } else if (slide != null && slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) { message = getResources().getString(R.string.ConversationItem_pending); action = () -> {}; @@ -1083,11 +1094,11 @@ public class ConversationItem extends LinearLayout action = () -> singleDownloadClickListener.onClick(bodyText, slide); } else { message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); + action = () -> eventListener.onMoreTextClicked(conversationRecipient.getId(), messageRecord.getId(), messageRecord.isMms()); } } else { message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); + action = () -> eventListener.onMoreTextClicked(conversationRecipient.getId(), messageRecord.getId(), messageRecord.isMms()); } SpannableStringBuilder span = new SpannableStringBuilder(message); @@ -1108,16 +1119,6 @@ public class ConversationItem extends LinearLayout return span; } - @Override - public void onModified(final Recipient modified) { - Util.runOnMain(() -> { - setBubbleState(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); - setAudioViewTint(messageRecord, conversationRecipient); - }); - } - private class SharedContactEventListener implements SharedContactView.EventListener { @Override public void onAddToContactsClicked(@NonNull Contact contact) { @@ -1251,7 +1252,7 @@ public class ConversationItem extends LinearLayout Intent intent = new Intent(context, MediaPreviewActivity.class); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(slide.getUri(), slide.getContentType()); - intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); + intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, conversationRecipient.getId()); intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, messageRecord.isOutgoing()); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getTimestamp()); intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize()); @@ -1309,7 +1310,7 @@ public class ConversationItem extends LinearLayout intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, messageRecord.getThreadId()); intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, groupThread && messageRecord.isPush()); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); + intent.putExtra(MessageDetailsActivity.RECIPIENT_EXTRA, conversationRecipient.getId()); context.startActivity(intent); } else if (!messageRecord.isOutgoing() && messageRecord.isIdentityMismatchFailure()) { handleApproveIdentity(); @@ -1352,7 +1353,7 @@ public class ConversationItem extends LinearLayout ApplicationContext.getInstance(context) .getJobManager() .add(new SmsSendJob(context, messageRecord.getId(), - messageRecord.getIndividualRecipient().getAddress())); + messageRecord.getIndividualRecipient())); } }); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java index 9673f000e4..1c4455de47 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java @@ -84,7 +84,7 @@ public class ConversationPopupActivity extends ConversationActivity { public void onSuccess(Long result) { ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height); Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, getRecipient().getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, getRecipient().getId()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, result); startActivity(intent, transition.toBundle()); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java index cd1eaeb362..cbe6f55429 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java @@ -105,7 +105,7 @@ public class ConversationTitleView extends RelativeLayout { } private void setRecipientTitle(Recipient recipient) { - if (recipient.isGroupRecipient()) setGroupRecipientTitle(recipient); + if (recipient.isGroup()) setGroupRecipientTitle(recipient); else if (recipient.isLocalNumber()) setSelfTitle(); else if (TextUtils.isEmpty(recipient.getName())) setNonContactRecipientTitle(recipient); else setContactRecipientTitle(recipient); @@ -116,7 +116,7 @@ public class ConversationTitleView extends RelativeLayout { this.title.setText(recipient.getName()); this.subtitle.setText(Stream.of(recipient.getParticipants()) - .filter(r -> !r.getAddress().serialize().equals(localNumber)) + .filter(r -> !r.requireAddress().serialize().equals(localNumber)) .map(Recipient::toShortString) .collect(Collectors.joining(", "))); @@ -130,7 +130,7 @@ public class ConversationTitleView extends RelativeLayout { @SuppressLint("SetTextI18n") private void setNonContactRecipientTitle(Recipient recipient) { - this.title.setText(recipient.getAddress().serialize()); + this.title.setText(recipient.requireAddress().serialize()); if (TextUtils.isEmpty(recipient.getProfileName())) { this.subtitle.setText(null); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index 8dbeca26b8..7c4898596b 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -22,13 +22,13 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.whispersystems.libsignal.util.guava.Optional; @@ -37,7 +37,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; public class ConversationUpdateItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem + implements RecipientForeverObserver, BindableConversationItem { private static final String TAG = ConversationUpdateItem.class.getSimpleName(); @@ -47,7 +47,7 @@ public class ConversationUpdateItem extends LinearLayout private TextView title; private TextView body; private TextView date; - private Recipient sender; + private LiveRecipient sender; private MessageRecord messageRecord; private Locale locale; @@ -98,12 +98,28 @@ public class ConversationUpdateItem extends LinearLayout } private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { + if (this.sender != null) { + this.sender.removeForeverObserver(this); + } + + if (this.messageRecord != null && messageRecord.isGroupAction()) { + GroupUtil.getDescription(getContext(), messageRecord.getBody()).removeObserver(this); + } + this.messageRecord = messageRecord; - this.sender = messageRecord.getIndividualRecipient(); + this.sender = messageRecord.getIndividualRecipient().live(); this.locale = locale; - this.sender.addListener(this); + this.sender.observeForever(this); + if (this.messageRecord != null && messageRecord.isGroupAction()) { + GroupUtil.getDescription(getContext(), messageRecord.getBody()).addObserver(this); + } + + present(messageRecord); + } + + private void present(MessageRecord messageRecord) { if (messageRecord.isGroupAction()) setGroupRecord(messageRecord); else if (messageRecord.isCallLog()) setCallRecord(messageRecord); else if (messageRecord.isJoined()) setJoinedRecord(messageRecord); @@ -174,7 +190,6 @@ public class ConversationUpdateItem extends LinearLayout icon.setImageResource(R.drawable.ic_group_grey600_24dp); icon.clearColorFilter(); - GroupUtil.getDescription(getContext(), messageRecord.getBody()).addListener(this); body.setText(messageRecord.getDisplayBody(getContext())); title.setVisibility(GONE); @@ -203,8 +218,8 @@ public class ConversationUpdateItem extends LinearLayout } @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> bind(messageRecord, locale)); + public void onRecipientChanged(@NonNull Recipient recipient) { + present(messageRecord); } @Override @@ -215,7 +230,10 @@ public class ConversationUpdateItem extends LinearLayout @Override public void unbind() { if (sender != null) { - sender.removeListener(this); + sender.removeForeverObserver(this); + } + if (this.messageRecord != null && messageRecord.isGroupAction()) { + GroupUtil.getDescription(getContext(), messageRecord.getBody()).removeObserver(this); } } @@ -238,14 +256,14 @@ public class ConversationUpdateItem extends LinearLayout return; } - final Recipient sender = ConversationUpdateItem.this.sender; + final Recipient sender = ConversationUpdateItem.this.sender.get(); IdentityUtil.getRemoteIdentityKey(getContext(), sender).addListener(new ListenableFuture.Listener>() { @Override public void onSuccess(Optional result) { if (result.isPresent()) { Intent intent = new Intent(getContext(), VerifyIdentityActivity.class); - intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, sender.getAddress()); + intent.putExtra(VerifyIdentityActivity.RECIPIENT_EXTRA, sender.getId()); intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(result.get().getIdentityKey())); intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, result.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED); diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index 13b1becf8f..edf308af25 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -10,7 +10,9 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; @@ -47,12 +49,12 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) { synchronized (LOCK) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - Address signalAddress = Address.fromExternal(context, address.getName()); - Optional identityRecord = identityDatabase.getIdentity(signalAddress); + Recipient recipient = Recipient.external(context, address.getName()); + Optional identityRecord = identityDatabase.getIdentity(recipient.getId()); if (!identityRecord.isPresent()) { Log.i(TAG, "Saving new identity..."); - identityDatabase.saveIdentity(signalAddress, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); + identityDatabase.saveIdentity(recipient.getId(), identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); return false; } @@ -68,15 +70,15 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { verifiedStatus = VerifiedStatus.DEFAULT; } - identityDatabase.saveIdentity(signalAddress, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); - IdentityUtil.markIdentityUpdate(context, Recipient.from(context, signalAddress, true)); + identityDatabase.saveIdentity(recipient.getId(), identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); + IdentityUtil.markIdentityUpdate(context, recipient); SessionUtil.archiveSiblingSessions(context, address); return true; } if (isNonBlockingApprovalRequired(identityRecord.get())) { Log.i(TAG, "Setting approval status..."); - identityDatabase.setApproval(signalAddress, nonBlockingApproval); + identityDatabase.setApproval(recipient.getId(), nonBlockingApproval); return false; } @@ -93,15 +95,15 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { synchronized (LOCK) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - String ourNumber = TextSecurePreferences.getLocalNumber(context); - Address theirAddress = Address.fromExternal(context, address.getName()); + RecipientId ourRecipientId = Recipient.self().getId(); + RecipientId theirRecipientId = Recipient.external(context, address.getName()).getId(); - if (ourNumber.equals(address.getName()) || Address.fromSerialized(ourNumber).equals(theirAddress)) { + if (ourRecipientId.equals(theirRecipientId)) { return identityKey.equals(IdentityKeyUtil.getIdentityKey(context)); } switch (direction) { - case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirAddress)); + case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirRecipientId)); case RECEIVING: return true; default: throw new AssertionError("Unknown direction: " + direction); } @@ -110,7 +112,8 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { @Override public IdentityKey getIdentity(SignalProtocolAddress address) { - Optional record = DatabaseFactory.getIdentityDatabase(context).getIdentity(Address.fromSerialized(address.getName())); + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + Optional record = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipientId); if (record.isPresent()) { return record.get().getIdentityKey(); diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index 77ee9bb488..740dd0e8eb 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.state.SessionRecord; @@ -29,7 +31,8 @@ public class TextSecureSessionStore implements SessionStore { @Override public SessionRecord loadSession(@NonNull SignalProtocolAddress address) { synchronized (FILE_LOCK) { - SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.fromSerialized(address.getName()), address.getDeviceId()); + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); if (sessionRecord == null) { Log.w(TAG, "No existing session information found."); @@ -43,14 +46,16 @@ public class TextSecureSessionStore implements SessionStore { @Override public void storeSession(@NonNull SignalProtocolAddress address, @NonNull SessionRecord record) { synchronized (FILE_LOCK) { - DatabaseFactory.getSessionDatabase(context).store(Address.fromSerialized(address.getName()), address.getDeviceId(), record); + RecipientId id = Recipient.external(context, address.getName()).getId(); + DatabaseFactory.getSessionDatabase(context).store(id, address.getDeviceId(), record); } } @Override public boolean containsSession(SignalProtocolAddress address) { synchronized (FILE_LOCK) { - SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.fromSerialized(address.getName()), address.getDeviceId()); + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); return sessionRecord != null && sessionRecord.getSessionState().hasSenderChain() && @@ -61,32 +66,36 @@ public class TextSecureSessionStore implements SessionStore { @Override public void deleteSession(SignalProtocolAddress address) { synchronized (FILE_LOCK) { - DatabaseFactory.getSessionDatabase(context).delete(Address.fromSerialized(address.getName()), address.getDeviceId()); + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + DatabaseFactory.getSessionDatabase(context).delete(recipientId, address.getDeviceId()); } } @Override public void deleteAllSessions(String name) { synchronized (FILE_LOCK) { - DatabaseFactory.getSessionDatabase(context).deleteAllFor(Address.fromSerialized(name)); + RecipientId recipientId = Recipient.external(context, name).getId(); + DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipientId); } } @Override public List getSubDeviceSessions(String name) { synchronized (FILE_LOCK) { - return DatabaseFactory.getSessionDatabase(context).getSubDevices(Address.fromSerialized(name)); + RecipientId recipientId = Recipient.external(context, name).getId(); + return DatabaseFactory.getSessionDatabase(context).getSubDevices(recipientId); } } public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) { synchronized (FILE_LOCK) { - List sessions = DatabaseFactory.getSessionDatabase(context).getAllFor(Address.fromSerialized(address.getName())); + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + List sessions = DatabaseFactory.getSessionDatabase(context).getAllFor(recipientId); for (SessionDatabase.SessionRow row : sessions) { if (row.getDeviceId() != address.getDeviceId()) { row.getRecord().archiveCurrentState(); - storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord()); + storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireAddress().serialize(), row.getDeviceId()), row.getRecord()); } } } @@ -98,7 +107,7 @@ public class TextSecureSessionStore implements SessionStore { for (SessionDatabase.SessionRow row : sessions) { row.getRecord().archiveCurrentState(); - storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord()); + storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireAddress().serialize(), row.getDeviceId()), row.getRecord()); } } } diff --git a/src/org/thoughtcrime/securesms/database/Address.java b/src/org/thoughtcrime/securesms/database/Address.java index 2920c0a272..59ae27b76a 100644 --- a/src/org/thoughtcrime/securesms/database/Address.java +++ b/src/org/thoughtcrime/securesms/database/Address.java @@ -1,36 +1,12 @@ package org.thoughtcrime.securesms.database; -import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.Pair; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber; -import com.google.i18n.phonenumbers.ShortNumberInfo; - -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.util.NumberUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.util.guava.Optional; - -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; public class Address implements Parcelable, Comparable
{ @@ -48,8 +24,6 @@ public class Address implements Parcelable, Comparable
{ private static final String TAG = Address.class.getSimpleName(); - private static final AtomicReference> cachedFormatter = new AtomicReference<>(); - private final String address; private Address(@NonNull String address) { @@ -65,50 +39,6 @@ public class Address implements Parcelable, Comparable
{ return new Address(serialized); } - public static Address fromExternal(@NonNull Context context, @Nullable String external) { - return new Address(getExternalAddressFormatter(context).format(external)); - } - - public static @NonNull List
fromSerializedList(@NonNull String serialized, char delimiter) { - String[] escapedAddresses = DelimiterUtil.split(serialized, delimiter); - List
addresses = new LinkedList<>(); - - for (String escapedAddress : escapedAddresses) { - addresses.add(Address.fromSerialized(DelimiterUtil.unescape(escapedAddress, delimiter))); - } - - return addresses; - } - - public static @NonNull String toSerializedList(@NonNull List
addresses, char delimiter) { - Collections.sort(addresses); - - List escapedAddresses = new LinkedList<>(); - - for (Address address : addresses) { - escapedAddresses.add(DelimiterUtil.escape(address.serialize(), delimiter)); - } - - return Util.join(escapedAddresses, delimiter + ""); - } - - private static @NonNull ExternalAddressFormatter getExternalAddressFormatter(Context context) { - String localNumber = TextSecurePreferences.getLocalNumber(context); - - if (!TextUtils.isEmpty(localNumber)) { - Pair cached = cachedFormatter.get(); - - if (cached != null && cached.first.equals(localNumber)) return cached.second; - - ExternalAddressFormatter formatter = new ExternalAddressFormatter(localNumber); - cachedFormatter.set(new Pair<>(localNumber, formatter)); - - return formatter; - } else { - return new ExternalAddressFormatter(Util.getSimCountryIso(context).or("US"), true); - } - } - public boolean isGroup() { return GroupUtil.isEncodedGroup(address); } @@ -179,160 +109,4 @@ public class Address implements Parcelable, Comparable
{ public int compareTo(@NonNull Address other) { return address.compareTo(other.address); } - - @VisibleForTesting - public static class ExternalAddressFormatter { - - private static final String TAG = ExternalAddressFormatter.class.getSimpleName(); - - private static final Set SHORT_COUNTRIES = new HashSet() {{ - add("NU"); - add("TK"); - add("NC"); - add("AC"); - }}; - - private static final Pattern US_NO_AREACODE = Pattern.compile("^(\\d{7})$"); - private static final Pattern BR_NO_AREACODE = Pattern.compile("^(9?\\d{8})$"); - - private final Optional localNumber; - private final String localCountryCode; - - private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); - - ExternalAddressFormatter(@NonNull String localNumberString) { - try { - Phonenumber.PhoneNumber libNumber = phoneNumberUtil.parse(localNumberString, null); - int countryCode = libNumber.getCountryCode(); - - this.localNumber = Optional.of(new PhoneNumber(localNumberString, countryCode, parseAreaCode(localNumberString, countryCode))); - this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(libNumber); - } catch (NumberParseException e) { - throw new AssertionError(e); - } - } - - ExternalAddressFormatter(@NonNull String localCountryCode, boolean countryCode) { - this.localNumber = Optional.absent(); - this.localCountryCode = localCountryCode; - } - - public String format(@Nullable String number) { - if (number == null) return "Unknown"; - if (GroupUtil.isEncodedGroup(number)) return number; - if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); - - String bareNumber = number.replaceAll("[^0-9+]", ""); - - if (bareNumber.length() == 0) { - if (number.trim().length() == 0) return "Unknown"; - else return number.trim(); - } - - // libphonenumber doesn't seem to be correct for Germany and Finland - if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) { - return bareNumber; - } - - // libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes. - if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) { - return bareNumber; - } - - if (isShortCode(bareNumber, localCountryCode)) { - return bareNumber; - } - - String processedNumber = applyAreaCodeRules(localNumber, bareNumber); - - try { - Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(processedNumber, localCountryCode); - return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); - } catch (NumberParseException e) { - Log.w(TAG, e); - if (bareNumber.charAt(0) == '+') - return bareNumber; - - String localNumberImprecise = localNumber.isPresent() ? localNumber.get().getE164Number() : ""; - - if (localNumberImprecise.charAt(0) == '+') - localNumberImprecise = localNumberImprecise.substring(1); - - if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) - return "+" + number; - - int difference = localNumberImprecise.length() - bareNumber.length(); - - return "+" + localNumberImprecise.substring(0, difference) + bareNumber; - } - } - - private boolean isShortCode(@NonNull String bareNumber, String localCountryCode) { - try { - Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode); - return ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode); - } catch (NumberParseException e) { - return false; - } - } - - private @Nullable String parseAreaCode(@NonNull String e164Number, int countryCode) { - switch (countryCode) { - case 1: - return e164Number.substring(2, 5); - case 55: - return e164Number.substring(3, 5); - } - return null; - } - - - private @NonNull String applyAreaCodeRules(@NonNull Optional localNumber, @NonNull String testNumber) { - if (!localNumber.isPresent() || !localNumber.get().getAreaCode().isPresent()) { - return testNumber; - } - - Matcher matcher; - switch (localNumber.get().getCountryCode()) { - case 1: - matcher = US_NO_AREACODE.matcher(testNumber); - if (matcher.matches()) { - return localNumber.get().getAreaCode() + matcher.group(); - } - break; - - case 55: - matcher = BR_NO_AREACODE.matcher(testNumber); - if (matcher.matches()) { - return localNumber.get().getAreaCode() + matcher.group(); - } - } - return testNumber; - } - - private static class PhoneNumber { - private final String e164Number; - private final int countryCode; - private final Optional areaCode; - - PhoneNumber(String e164Number, int countryCode, @Nullable String areaCode) { - this.e164Number = e164Number; - this.countryCode = countryCode; - this.areaCode = Optional.fromNullable(areaCode); - } - - String getE164Number() { - return e164Number; - } - - int getCountryCode() { - return countryCode; - } - - Optional getAreaCode() { - return areaCode; - } - } - } } diff --git a/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java b/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java index c33cef405d..edca8df424 100644 --- a/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java +++ b/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java @@ -1,6 +1,9 @@ package org.thoughtcrime.securesms.database; +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.LRUCache; import java.util.HashMap; @@ -11,13 +14,17 @@ public class EarlyReceiptCache { private static final String TAG = EarlyReceiptCache.class.getSimpleName(); - private final LRUCache> cache = new LRUCache<>(100); + private final LRUCache> cache = new LRUCache<>(100); + private final String name; - public synchronized void increment(long timestamp, Address origin) { - Log.i(TAG, this+""); - Log.i(TAG, String.format(Locale.US, "Early receipt: (%d, %s)", timestamp, origin.serialize())); + public EarlyReceiptCache(@NonNull String name) { + this.name = name; + } - Map receipts = cache.get(timestamp); + public synchronized void increment(long timestamp, @NonNull RecipientId origin) { + Log.i(TAG, String.format(Locale.US, "[%s] Timestamp: %d, Recipient: %s", name, timestamp, origin.serialize())); + + Map receipts = cache.get(timestamp); if (receipts == null) { receipts = new HashMap<>(); @@ -34,8 +41,8 @@ public class EarlyReceiptCache { cache.put(timestamp, receipts); } - public synchronized Map remove(long timestamp) { - Map receipts = cache.remove(timestamp); + public synchronized Map remove(long timestamp) { + Map receipts = cache.remove(timestamp); Log.i(TAG, this+""); Log.i(TAG, String.format(Locale.US, "Checking early receipts (%d): %d", timestamp, receipts == null ? 0 : receipts.size())); diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 607ba66ca2..9fce6e84cd 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -15,7 +15,9 @@ import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; @@ -37,6 +39,7 @@ public class GroupDatabase extends Database { static final String TABLE_NAME = "groups"; private static final String ID = "_id"; static final String GROUP_ID = "group_id"; + static final String RECIPIENT_ID = "recipient_id"; private static final String TITLE = "title"; private static final String MEMBERS = "members"; private static final String AVATAR = "avatar"; @@ -53,6 +56,7 @@ public class GroupDatabase extends Database { "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + GROUP_ID + " TEXT, " + + RECIPIENT_ID + " INTEGER, " + TITLE + " TEXT, " + MEMBERS + " TEXT, " + AVATAR + " BLOB, " + @@ -67,10 +71,11 @@ public class GroupDatabase extends Database { public static final String[] CREATE_INDEXS = { "CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");", + "CREATE UNIQUE INDEX IF NOT EXISTS group_recipient_id_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");", }; private static final String[] GROUP_PROJECTION = { - GROUP_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, + GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, TIMESTAMP, ACTIVE, MMS }; @@ -80,6 +85,16 @@ public class GroupDatabase extends Database { super(context, databaseHelper); } + public Optional getGroup(RecipientId recipientId) { + try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, RECIPIENT_ID + " = ?", new String[] {recipientId.serialize()}, null, null, null)) { + if (cursor != null && cursor.moveToNext()) { + return getGroup(cursor); + } + + return Optional.absent(); + } + } + public Optional getGroup(String groupId) { try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", new String[] {groupId}, @@ -111,12 +126,12 @@ public class GroupDatabase extends Database { return new Reader(cursor); } - public String getOrCreateGroupForMembers(List
members, boolean mms) { + public String getOrCreateGroupForMembers(List members, boolean mms) { Collections.sort(members); Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {GROUP_ID}, MEMBERS + " = ? AND " + MMS + " = ?", - new String[] {Address.toSerializedList(members, ','), mms ? "1" : "0"}, + new String[] {RecipientId.toSerializedList(members), mms ? "1" : "0"}, null, null, null); try { if (cursor != null && cursor.moveToNext()) { @@ -138,28 +153,30 @@ public class GroupDatabase extends Database { } public @NonNull List getGroupMembers(String groupId, boolean includeSelf) { - List
members = getCurrentMembers(groupId); - List recipients = new LinkedList<>(); + List members = getCurrentMembers(groupId); + List recipients = new LinkedList<>(); - for (Address member : members) { - if (!includeSelf && Util.isOwnNumber(context, member)) + for (RecipientId member : members) { + if (!includeSelf && Recipient.resolved(member).isLocalNumber()) { continue; + } - recipients.add(Recipient.from(context, member, false)); + recipients.add(Recipient.resolved(member)); } return recipients; } - public void create(@NonNull String groupId, @Nullable String title, @NonNull List
members, + public void create(@NonNull String groupId, @Nullable String title, @NonNull List members, @Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay) { Collections.sort(members); ContentValues contentValues = new ContentValues(); + contentValues.put(RECIPIENT_ID, DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId).serialize()); contentValues.put(GROUP_ID, groupId); contentValues.put(TITLE, title); - contentValues.put(MEMBERS, Address.toSerializedList(members, ',')); + contentValues.put(MEMBERS, RecipientId.toSerializedList(members)); if (avatar != null) { contentValues.put(AVATAR_ID, avatar.getId()); @@ -175,11 +192,8 @@ public class GroupDatabase extends Database { databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setName(title); - recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null); - recipient.setParticipants(Stream.of(members).map(memberAddress -> Recipient.from(context, memberAddress, true)).toList()); - }); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); notifyConversationListListeners(); } @@ -199,10 +213,8 @@ public class GroupDatabase extends Database { GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setName(title); - recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null); - }); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); notifyConversationListListeners(); } @@ -213,8 +225,8 @@ public class GroupDatabase extends Database { databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId}); - Recipient recipient = Recipient.from(context, Address.fromSerialized(groupId), false); - recipient.setName(title); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); } public void updateAvatar(String groupId, Bitmap avatar) { @@ -235,44 +247,39 @@ public class GroupDatabase extends Database { databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId)); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); } - public void updateMembers(String groupId, List
members) { + public void updateMembers(String groupId, List members) { Collections.sort(members); ContentValues contents = new ContentValues(); - contents.put(MEMBERS, Address.toSerializedList(members, ',')); + contents.put(MEMBERS, RecipientId.toSerializedList(members)); contents.put(ACTIVE, 1); databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setParticipants(Stream.of(members).map(a -> Recipient.from(context, a, false)).toList()); - }); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); } - public void remove(String groupId, Address source) { - List
currentMembers = getCurrentMembers(groupId); + public void remove(String groupId, RecipientId source) { + List currentMembers = getCurrentMembers(groupId); currentMembers.remove(source); ContentValues contents = new ContentValues(); - contents.put(MEMBERS, Address.toSerializedList(currentMembers, ',')); + contents.put(MEMBERS, RecipientId.toSerializedList(currentMembers)); databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - List current = recipient.getParticipants(); - Recipient removal = Recipient.from(context, source, false); - - current.remove(removal); - recipient.setParticipants(current); - }); + RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient.live(groupRecipient).refresh(); } - private List
getCurrentMembers(String groupId) { + private List getCurrentMembers(String groupId) { Cursor cursor = null; try { @@ -283,7 +290,7 @@ public class GroupDatabase extends Database { if (cursor != null && cursor.moveToFirst()) { String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)); - return Address.fromSerializedList(serializedMembers, ','); + return RecipientId.fromSerializedList(serializedMembers); } return new LinkedList<>(); @@ -334,6 +341,7 @@ public class GroupDatabase extends Database { } return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)), + RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))), cursor.getString(cursor.getColumnIndexOrThrow(TITLE)), cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR)), @@ -355,23 +363,25 @@ public class GroupDatabase extends Database { public static class GroupRecord { - private final String id; - private final String title; - private final List
members; - private final byte[] avatar; - private final long avatarId; - private final byte[] avatarKey; - private final byte[] avatarDigest; - private final String avatarContentType; - private final String relay; - private final boolean active; - private final boolean mms; + private final String id; + private final RecipientId recipientId; + private final String title; + private final List members; + private final byte[] avatar; + private final long avatarId; + private final byte[] avatarKey; + private final byte[] avatarDigest; + private final String avatarContentType; + private final String relay; + private final boolean active; + private final boolean mms; - public GroupRecord(String id, String title, String members, byte[] avatar, + public GroupRecord(String id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar, long avatarId, byte[] avatarKey, String avatarContentType, String relay, boolean active, byte[] avatarDigest, boolean mms) { this.id = id; + this.recipientId = recipientId; this.title = title; this.avatar = avatar; this.avatarId = avatarId; @@ -382,7 +392,7 @@ public class GroupDatabase extends Database { this.active = active; this.mms = mms; - if (!TextUtils.isEmpty(members)) this.members = Address.fromSerializedList(members, ','); + if (!TextUtils.isEmpty(members)) this.members = RecipientId.fromSerializedList(members); else this.members = new LinkedList<>(); } @@ -394,6 +404,10 @@ public class GroupDatabase extends Database { } } + public @NonNull RecipientId getRecipientId() { + return recipientId; + } + public String getEncodedId() { return id; } @@ -402,7 +416,7 @@ public class GroupDatabase extends Database { return title; } - public List
getMembers() { + public List getMembers() { return members; } diff --git a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index 4d83184a57..440e4ee3c0 100644 --- a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -9,6 +9,7 @@ import androidx.annotation.NonNull; import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.util.LinkedList; import java.util.List; @@ -19,7 +20,7 @@ public class GroupReceiptDatabase extends Database { private static final String ID = "_id"; public static final String MMS_ID = "mms_id"; - private static final String ADDRESS = "address"; + private static final String RECIPIENT_ID = "address"; private static final String STATUS = "status"; private static final String TIMESTAMP = "timestamp"; private static final String UNIDENTIFIED = "unidentified"; @@ -30,7 +31,7 @@ public class GroupReceiptDatabase extends Database { public static final int STATUS_READ = 2; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER, " + UNIDENTIFIED + " INTEGER DEFAULT 0);"; + MMS_ID + " INTEGER, " + RECIPIENT_ID + " INTEGER, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER, " + UNIDENTIFIED + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXES = { "CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", @@ -40,13 +41,13 @@ public class GroupReceiptDatabase extends Database { super(context, databaseHelper); } - public void insert(List
addresses, long mmsId, int status, long timestamp) { + public void insert(List recipientIds, long mmsId, int status, long timestamp) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); - for (Address address : addresses) { + for (RecipientId recipientId : recipientIds) { ContentValues values = new ContentValues(4); values.put(MMS_ID, mmsId); - values.put(ADDRESS, address.serialize()); + values.put(RECIPIENT_ID, recipientId.serialize()); values.put(STATUS, status); values.put(TIMESTAMP, timestamp); @@ -54,23 +55,23 @@ public class GroupReceiptDatabase extends Database { } } - public void update(Address address, long mmsId, int status, long timestamp) { + public void update(@NonNull RecipientId recipientId, long mmsId, int status, long timestamp) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(2); values.put(STATUS, status); values.put(TIMESTAMP, timestamp); - db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + ADDRESS + " = ? AND " + STATUS + " < ?", - new String[] {String.valueOf(mmsId), address.serialize(), String.valueOf(status)}); + db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + RECIPIENT_ID + " = ? AND " + STATUS + " < ?", + new String[] {String.valueOf(mmsId), recipientId.serialize(), String.valueOf(status)}); } - public void setUnidentified(Address address, long mmsId, boolean unidentified) { + public void setUnidentified(RecipientId recipientId, long mmsId, boolean unidentified) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(1); values.put(UNIDENTIFIED, unidentified ? 1 : 0); - db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + ADDRESS + " = ?", - new String[] {String.valueOf(mmsId), address.serialize()}); + db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + RECIPIENT_ID + " = ?", + new String[] {String.valueOf(mmsId), recipientId.serialize()}); } @@ -80,7 +81,7 @@ public class GroupReceiptDatabase extends Database { try (Cursor cursor = db.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)}, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))), + results.add(new GroupReceiptInfo(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))), cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1)); @@ -101,20 +102,20 @@ public class GroupReceiptDatabase extends Database { } public static class GroupReceiptInfo { - private final Address address; - private final int status; - private final long timestamp; - private final boolean unidentified; + private final RecipientId recipientId; + private final int status; + private final long timestamp; + private final boolean unidentified; - GroupReceiptInfo(Address address, int status, long timestamp, boolean unidentified) { - this.address = address; + GroupReceiptInfo(@NonNull RecipientId recipientId, int status, long timestamp, boolean unidentified) { + this.recipientId = recipientId; this.status = status; this.timestamp = timestamp; this.unidentified = unidentified; } - public Address getAddress() { - return address; + public @NonNull RecipientId getRecipientId() { + return recipientId; } public int getStatus() { diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index c3d88e8674..ce313c7fd3 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -26,6 +26,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; @@ -40,7 +41,7 @@ public class IdentityDatabase extends Database { private static final String TABLE_NAME = "identities"; private static final String ID = "_id"; - private static final String ADDRESS = "address"; + private static final String RECIPIENT_ID = "address"; private static final String IDENTITY_KEY = "key"; private static final String TIMESTAMP = "timestamp"; private static final String FIRST_USE = "first_use"; @@ -49,7 +50,7 @@ public class IdentityDatabase extends Database { public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - ADDRESS + " TEXT UNIQUE, " + + RECIPIENT_ID + " INTEGER UNIQUE, " + IDENTITY_KEY + " TEXT, " + FIRST_USE + " INTEGER DEFAULT 0, " + TIMESTAMP + " INTEGER DEFAULT 0, " + @@ -88,13 +89,13 @@ public class IdentityDatabase extends Database { return new IdentityReader(cursor); } - public Optional getIdentity(Address address) { + public Optional getIdentity(@NonNull RecipientId recipientId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = null; try { - cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", - new String[] {address.serialize()}, null, null, null); + cursor = database.query(TABLE_NAME, null, RECIPIENT_ID + " = ?", + new String[] {recipientId.serialize()}, null, null, null); if (cursor != null && cursor.moveToFirst()) { return Optional.of(getIdentityRecord(cursor)); @@ -108,14 +109,14 @@ public class IdentityDatabase extends Database { return Optional.absent(); } - public void saveIdentity(Address address, IdentityKey identityKey, VerifiedStatus verifiedStatus, + public void saveIdentity(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); String identityKeyString = Base64.encodeBytes(identityKey.serialize()); ContentValues contentValues = new ContentValues(); - contentValues.put(ADDRESS, address.serialize()); + contentValues.put(RECIPIENT_ID, recipientId.serialize()); contentValues.put(IDENTITY_KEY, identityKeyString); contentValues.put(TIMESTAMP, timestamp); contentValues.put(VERIFIED, verifiedStatus.toInt()); @@ -124,36 +125,36 @@ public class IdentityDatabase extends Database { database.replace(TABLE_NAME, null, contentValues); - EventBus.getDefault().post(new IdentityRecord(address, identityKey, verifiedStatus, + EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)); } - public void setApproval(Address address, boolean nonBlockingApproval) { + public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(2); contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval); - database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", new String[] {address.serialize()}); + database.update(TABLE_NAME, contentValues, RECIPIENT_ID + " = ?", new String[] {recipientId.serialize()}); } - public void setVerified(Address address, IdentityKey identityKey, VerifiedStatus verifiedStatus) { + public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(1); contentValues.put(VERIFIED, verifiedStatus.toInt()); - int updated = database.update(TABLE_NAME, contentValues, ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?", - new String[] {address.serialize(), Base64.encodeBytes(identityKey.serialize())}); + int updated = database.update(TABLE_NAME, contentValues, RECIPIENT_ID + " = ? AND " + IDENTITY_KEY + " = ?", + new String[] {recipientId.serialize(), Base64.encodeBytes(identityKey.serialize())}); if (updated > 0) { - Optional record = getIdentity(address); + Optional record = getIdentity(recipientId); if (record.isPresent()) EventBus.getDefault().post(record.get()); } } private IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException { - String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)); String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); int verifiedStatus = cursor.getInt(cursor.getColumnIndexOrThrow(VERIFIED)); @@ -161,23 +162,23 @@ public class IdentityDatabase extends Database { boolean firstUse = cursor.getInt(cursor.getColumnIndexOrThrow(FIRST_USE)) == 1; IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0); - return new IdentityRecord(Address.fromSerialized(address), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval); + return new IdentityRecord(RecipientId.from(recipientId), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval); } public static class IdentityRecord { - private final Address address; + private final RecipientId recipientId; private final IdentityKey identitykey; private final VerifiedStatus verifiedStatus; private final boolean firstUse; private final long timestamp; private final boolean nonblockingApproval; - private IdentityRecord(Address address, + private IdentityRecord(@NonNull RecipientId recipientId, IdentityKey identitykey, VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonblockingApproval) { - this.address = address; + this.recipientId = recipientId; this.identitykey = identitykey; this.verifiedStatus = verifiedStatus; this.firstUse = firstUse; @@ -185,8 +186,8 @@ public class IdentityDatabase extends Database { this.nonblockingApproval = nonblockingApproval; } - public Address getAddress() { - return address; + public RecipientId getRecipientId() { + return recipientId; } public IdentityKey getIdentityKey() { @@ -211,7 +212,7 @@ public class IdentityDatabase extends Database { @Override public @NonNull String toString() { - return "{address: " + address + ", identityKey: " + identitykey + ", verifiedStatus: " + verifiedStatus + ", firstUse: " + firstUse + "}"; + return "{recipientId: " + recipientId + ", identityKey: " + identitykey + ", verifiedStatus: " + verifiedStatus + ", firstUse: " + firstUse + "}"; } } diff --git a/src/org/thoughtcrime/securesms/database/MediaDatabase.java b/src/org/thoughtcrime/securesms/database/MediaDatabase.java index 7ba327c8da..7de7e5aa66 100644 --- a/src/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -10,6 +10,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.util.List; @@ -41,7 +42,7 @@ public class MediaDatabase extends Database { + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", " - + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ADDRESS + " " + + MmsDatabase.TABLE_NAME + "." + MmsDatabase.RECIPIENT_ID + " " + "FROM " + AttachmentDatabase.TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME + " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " + "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID @@ -88,27 +89,22 @@ public class MediaDatabase extends Database { public static class MediaRecord { private final DatabaseAttachment attachment; - private final Address address; + private final RecipientId recipientId; private final long date; private final boolean outgoing; - private MediaRecord(DatabaseAttachment attachment, @Nullable Address address, long date, boolean outgoing) { - this.attachment = attachment; - this.address = address; - this.date = date; - this.outgoing = outgoing; + private MediaRecord(DatabaseAttachment attachment, @NonNull RecipientId recipientId, long date, boolean outgoing) { + this.attachment = attachment; + this.recipientId = recipientId; + this.date = date; + this.outgoing = outgoing; } public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) { AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); List attachments = attachmentDatabase.getAttachment(cursor); - String serializedAddress = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); + RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.RECIPIENT_ID))); boolean outgoing = MessagingDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX))); - Address address = null; - - if (serializedAddress != null) { - address = Address.fromSerialized(serializedAddress); - } long date; @@ -118,7 +114,7 @@ public class MediaDatabase extends Database { date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE_RECEIVED)); } - return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, address, date, outgoing); + return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, recipientId, date, outgoing); } public DatabaseAttachment getAttachment() { @@ -129,8 +125,8 @@ public class MediaDatabase extends Database { return attachment.getContentType(); } - public @Nullable Address getAddress() { - return address; + public @NonNull RecipientId getRecipientId() { + return recipientId; } public long getDate() { diff --git a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java index 23bb816ac2..d48e5ca92e 100644 --- a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -13,11 +13,10 @@ import org.thoughtcrime.securesms.database.documents.Document; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; -import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.JsonUtils; import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; import java.util.ArrayList; @@ -40,41 +39,20 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); - public void setMismatchedIdentity(long messageId, final Address address, final IdentityKey identityKey) { - List items = new ArrayList() {{ - add(new IdentityKeyMismatch(address, identityKey)); - }}; - - IdentityKeyMismatchList document = new IdentityKeyMismatchList(items); - - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - database.beginTransaction(); - - try { - setDocument(database, messageId, MISMATCHED_IDENTITIES, document); - - database.setTransactionSuccessful(); - } catch (IOException ioe) { - Log.w(TAG, ioe); - } finally { - database.endTransaction(); - } - } - - public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { + public void addMismatchedIdentity(long messageId, @NonNull RecipientId recipientId, IdentityKey identityKey) { try { addToDocument(messageId, MISMATCHED_IDENTITIES, - new IdentityKeyMismatch(address, identityKey), + new IdentityKeyMismatch(recipientId, identityKey), IdentityKeyMismatchList.class); } catch (IOException e) { Log.w(TAG, e); } } - public void removeMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { + public void removeMismatchedIdentity(long messageId, @NonNull RecipientId recipientId, IdentityKey identityKey) { try { removeFromDocument(messageId, MISMATCHED_IDENTITIES, - new IdentityKeyMismatch(address, identityKey), + new IdentityKeyMismatch(recipientId, identityKey), IdentityKeyMismatchList.class); } catch (IOException e) { Log.w(TAG, e); @@ -178,16 +156,16 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public static class SyncMessageId { - private final Address address; - private final long timetamp; + private final RecipientId recipientId; + private final long timetamp; - public SyncMessageId(Address address, long timetamp) { - this.address = address; - this.timetamp = timetamp; + public SyncMessageId(@NonNull RecipientId recipientId, long timetamp) { + this.recipientId = recipientId; + this.timetamp = timetamp; } - public Address getAddress() { - return address; + public RecipientId getRecipientId() { + return recipientId; } public long getTimetamp() { diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 8fdafe582c..927c09399c 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -62,7 +62,7 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.thoughtcrime.securesms.mms.QuoteModel; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo; import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.util.JsonUtils; @@ -115,7 +115,7 @@ public class MmsDatabase extends MessagingDatabase { THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " + READ + " INTEGER DEFAULT 0, " + "m_id" + " TEXT, " + "sub" + " TEXT, " + "sub_cs" + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " + - "ct_t" + " TEXT, " + CONTENT_LOCATION + " TEXT, " + ADDRESS + " TEXT, " + + "ct_t" + " TEXT, " + CONTENT_LOCATION + " TEXT, " + RECIPIENT_ID + " INTEGER, " + ADDRESS_DEVICE_ID + " INTEGER, " + EXPIRY + " INTEGER, " + "m_cls" + " TEXT, " + MESSAGE_TYPE + " INTEGER, " + "v" + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + "pri" + " INTEGER, " + @@ -148,7 +148,7 @@ public class MmsDatabase extends MessagingDatabase { MESSAGE_BOX, READ, CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE, MESSAGE_SIZE, STATUS, TRANSACTION_ID, - BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, + BODY, PART_COUNT, RECIPIENT_ID, ADDRESS_DEVICE_ID, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, VIEW_ONCE, @@ -179,8 +179,8 @@ public class MmsDatabase extends MessagingDatabase { private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?"; - private final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); - private final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); + private final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache("MmsDelivery"); + private final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache("MmsRead"); public MmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -230,15 +230,15 @@ public class MmsDatabase extends MessagingDatabase { boolean found = false; try { - cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, ADDRESS}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); + cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); while (cursor.moveToNext()) { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) { - Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - Address ourAddress = messageId.getAddress(); - String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; + RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))); + RecipientId ourRecipientId = messageId.getRecipientId(); + String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; - if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) { + if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); int status = deliveryReceipt ? GroupReceiptDatabase.STATUS_DELIVERED : GroupReceiptDatabase.STATUS_READ; @@ -249,7 +249,7 @@ public class MmsDatabase extends MessagingDatabase { columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(id)}); - DatabaseFactory.getGroupReceiptDatabase(context).update(ourAddress, id, status, timestamp); + DatabaseFactory.getGroupReceiptDatabase(context).update(ourRecipientId, id, status, timestamp); DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); } @@ -257,8 +257,8 @@ public class MmsDatabase extends MessagingDatabase { } if (!found) { - if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); - if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId()); + if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId()); } } finally { if (cursor != null) @@ -285,12 +285,13 @@ public class MmsDatabase extends MessagingDatabase { } } - private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException { + private long getThreadIdFor(@NonNull IncomingMediaMessage retrieved) { if (retrieved.getGroupId() != null) { - Recipient groupRecipients = Recipient.from(context, retrieved.getGroupId(), true); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(retrieved.getGroupId()); + Recipient groupRecipients = Recipient.resolved(groupRecipientId); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients); } else { - Recipient sender = Recipient.from(context, retrieved.getFrom(), true); + Recipient sender = Recipient.resolved(retrieved.getFrom()); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(sender); } } @@ -299,7 +300,7 @@ public class MmsDatabase extends MessagingDatabase { String fromString = notification.getFrom() != null && notification.getFrom().getTextString() != null ? Util.toIsoString(notification.getFrom().getTextString()) : ""; - Recipient recipient = Recipient.from(context, Address.fromExternal(context, fromString), false); + Recipient recipient = Recipient.external(context, fromString); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); } @@ -462,11 +463,11 @@ public class MmsDatabase extends MessagingDatabase { database.beginTransaction(); try { - cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED}, where, arguments, null, null, null); + cursor = database.query(TABLE_NAME, new String[] {ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED}, where, arguments, null, null, null); while(cursor != null && cursor.moveToNext()) { if (Types.isSecureType(cursor.getLong(3))) { - SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(cursor.getString(1)), cursor.getLong(2)); + SyncMessageId syncMessageId = new SyncMessageId(RecipientId.from(cursor.getLong(1)), cursor.getLong(2)); ExpirationInfo expirationInfo = new ExpirationInfo(cursor.getLong(0), cursor.getLong(4), cursor.getLong(5), true); result.add(new MarkedMessageInfo(syncMessageId, expirationInfo)); @@ -492,13 +493,13 @@ public class MmsDatabase extends MessagingDatabase { Cursor cursor = null; try { - cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, ADDRESS}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); + cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, RECIPIENT_ID}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); while (cursor.moveToNext()) { - Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - Address ourAddress = messageId.getAddress(); + RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))); + RecipientId ourRecipientId = messageId.getRecipientId(); - if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) { + if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); @@ -583,7 +584,7 @@ public class MmsDatabase extends MessagingDatabase { cursor = rawQuery(RAW_ID_WHERE, new String[] {String.valueOf(messageId)}); if (cursor != null && cursor.moveToNext()) { - return Optional.of(new MmsNotificationInfo(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)), + return Optional.of(new MmsNotificationInfo(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), cursor.getString(cursor.getColumnIndexOrThrow(TRANSACTION_ID)), cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)))); @@ -614,14 +615,14 @@ public class MmsDatabase extends MessagingDatabase { int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); boolean viewOnce = cursor.getLong(cursor.getColumnIndexOrThrow(VIEW_ONCE)) == 1; - String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId); String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID)); - String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR)); + RecipientId quoteAuthor = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR))); String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY)); boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1; List quoteAttachments = Stream.of(associatedAttachments).filter(Attachment::isQuote).map(a -> (Attachment)a).toList(); @@ -634,13 +635,13 @@ public class MmsDatabase extends MessagingDatabase { .filterNot(previewAttachments::contains) .map(a -> (Attachment)a).toList(); - Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false); + Recipient recipient = Recipient.resolved(RecipientId.from(recipientId)); List networkFailures = new LinkedList<>(); List mismatches = new LinkedList<>(); QuoteModel quote = null; if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) { - quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments); + quote = new QuoteModel(quoteId, quoteAuthor, quoteText, quoteMissing, quoteAttachments); } if (!TextUtils.isEmpty(mismatchDocument)) { @@ -762,7 +763,7 @@ public class MmsDatabase extends MessagingDatabase { try { OutgoingMediaMessage request = getOutgoingMessage(messageId); ContentValues contentValues = new ContentValues(); - contentValues.put(ADDRESS, request.getRecipient().getAddress().serialize()); + contentValues.put(RECIPIENT_ID, request.getRecipient().getId().serialize()); contentValues.put(DATE_SENT, request.getSentTimeMillis()); contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT); contentValues.put(THREAD_ID, getThreadIdForMessage(messageId)); @@ -814,19 +815,13 @@ public class MmsDatabase extends MessagingDatabase { throws MmsException { if (threadId == -1 || retrieved.isGroupMessage()) { - try { - threadId = getThreadIdFor(retrieved); - } catch (RecipientFormattingException e) { - Log.w("MmsDatabase", e); - if (threadId == -1) - throw new MmsException(e); - } + threadId = getThreadIdFor(retrieved); } ContentValues contentValues = new ContentValues(); contentValues.put(DATE_SENT, retrieved.getSentTimeMillis()); - contentValues.put(ADDRESS, retrieved.getFrom().serialize()); + contentValues.put(RECIPIENT_ID, retrieved.getFrom().serialize()); contentValues.put(MESSAGE_BOX, mailbox); contentValues.put(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); @@ -924,7 +919,8 @@ public class MmsDatabase extends MessagingDatabase { contentBuilder.add(MESSAGE_TYPE, notification.getMessageType()); if (notification.getFrom() != null) { - contentValues.put(ADDRESS, Address.fromExternal(context, Util.toIsoString(notification.getFrom().getTextString())).serialize()); + Recipient recipient = Recipient.external(context, Util.toIsoString(notification.getFrom().getTextString())); + contentValues.put(RECIPIENT_ID, recipient.getId().serialize()); } contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE); @@ -980,8 +976,8 @@ public class MmsDatabase extends MessagingDatabase { type |= Types.EXPIRATION_TIMER_UPDATE_BIT; } - Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis()); - Map earlyReadReceipts = earlyReadReceiptCache.remove(message.getSentTimeMillis()); + Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis()); + Map earlyReadReceipts = earlyReadReceiptCache.remove(message.getSentTimeMillis()); ContentValues contentValues = new ContentValues(); contentValues.put(DATE_SENT, message.getSentTimeMillis()); @@ -994,7 +990,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(EXPIRES_IN, message.getExpiresIn()); contentValues.put(VIEW_ONCE, message.isViewOnce()); - contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize()); + contentValues.put(RECIPIENT_ID, message.getRecipient().getId().serialize()); contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum()); contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); @@ -1011,15 +1007,15 @@ public class MmsDatabase extends MessagingDatabase { long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), contentValues, insertListener); - if (message.getRecipient().getAddress().isGroup()) { - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false); + if (message.getRecipient().requireAddress().isGroup()) { + List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().requireAddress().toGroupString(), false); GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); - receiptDatabase.insert(Stream.of(members).map(Recipient::getAddress).toList(), + receiptDatabase.insert(Stream.of(members).map(Recipient::getId).toList(), messageId, defaultReceiptStatus, message.getSentTimeMillis()); - for (Address address : earlyDeliveryReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_DELIVERED, -1); - for (Address address : earlyReadReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_READ, -1); + for (RecipientId recipientId : earlyDeliveryReceipts.keySet()) receiptDatabase.update(recipientId, messageId, GroupReceiptDatabase.STATUS_DELIVERED, -1); + for (RecipientId recipientId : earlyReadReceipts.keySet()) receiptDatabase.update(recipientId, messageId, GroupReceiptDatabase.STATUS_READ, -1); } DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); @@ -1171,7 +1167,7 @@ public class MmsDatabase extends MessagingDatabase { private boolean isDuplicate(IncomingMediaMessage message, long threadId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?", + Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + RECIPIENT_ID + " = ? AND " + THREAD_ID + " = ?", new String[]{String.valueOf(message.getSentTimeMillis()), message.getFrom().serialize(), String.valueOf(threadId)}, null, null, null, "1"); @@ -1330,13 +1326,13 @@ public class MmsDatabase extends MessagingDatabase { } public static class MmsNotificationInfo { - private final Address from; - private final String contentLocation; - private final String transactionId; - private final int subscriptionId; + private final RecipientId from; + private final String contentLocation; + private final String transactionId; + private final int subscriptionId; - MmsNotificationInfo(@Nullable String from, String contentLocation, String transactionId, int subscriptionId) { - this.from = from == null ? null : Address.fromSerialized(from); + MmsNotificationInfo(@NonNull RecipientId from, String contentLocation, String transactionId, int subscriptionId) { + this.from = from; this.contentLocation = contentLocation; this.transactionId = transactionId; this.subscriptionId = subscriptionId; @@ -1354,7 +1350,7 @@ public class MmsDatabase extends MessagingDatabase { return subscriptionId; } - public @Nullable Address getFrom() { + public @NonNull RecipientId getFrom() { return from; } } @@ -1428,9 +1424,9 @@ public class MmsDatabase extends MessagingDatabase { long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); - String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); + long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.RECIPIENT_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); - Recipient recipient = getRecipientFor(address); + Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get(); String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION)); String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID)); @@ -1470,7 +1466,7 @@ public class MmsDatabase extends MessagingDatabase { long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); - String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); + long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.RECIPIENT_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT)); @@ -1488,7 +1484,7 @@ public class MmsDatabase extends MessagingDatabase { readReceiptCount = 0; } - Recipient recipient = getRecipientFor(address); + Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get(); List mismatches = getMismatchedIdentities(mismatchDocument); List networkFailures = getFailures(networkDocument); List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor); @@ -1506,18 +1502,6 @@ public class MmsDatabase extends MessagingDatabase { isViewOnce, readReceiptCount, quote, contacts, previews, unidentified); } - private Recipient getRecipientFor(String serialized) { - Address address; - - if (TextUtils.isEmpty(serialized) || "insert-address-token".equals(serialized)) { - address = Address.UNKNOWN; - } else { - address = Address.fromSerialized(serialized); - - } - return Recipient.from(context, address, true); - } - private List getMismatchedIdentities(String document) { if (!TextUtils.isEmpty(document)) { try { @@ -1551,15 +1535,15 @@ public class MmsDatabase extends MessagingDatabase { private @Nullable Quote getQuote(@NonNull Cursor cursor) { long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_ID)); - String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_AUTHOR)); + RecipientId quoteAuthor = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_AUTHOR))); String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_BODY)); boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_MISSING)) == 1; List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor); List quoteAttachments = Stream.of(attachments).filter(Attachment::isQuote).toList(); SlideDeck quoteDeck = new SlideDeck(context, quoteAttachments); - if (quoteId > 0 && !TextUtils.isEmpty(quoteAuthor)) { - return new Quote(quoteId, Address.fromExternal(context, quoteAuthor), quoteText, quoteMissing, quoteDeck); + if (quoteId > 0 && !quoteAuthor.isUnknown()) { + return new Quote(quoteId, quoteAuthor, quoteText, quoteMissing, quoteDeck); } else { return null; } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 7053d3bd93..74b01c5040 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -9,7 +9,7 @@ public interface MmsSmsColumns { public static final String THREAD_ID = "thread_id"; public static final String READ = "read"; public static final String BODY = "body"; - public static final String ADDRESS = "address"; + public static final String RECIPIENT_ID = "address"; public static final String ADDRESS_DEVICE_ID = "address_device_id"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; public static final String READ_RECEIPT_COUNT = "read_receipt_count"; diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index fd2962f2fd..37de59cef4 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -27,6 +27,8 @@ import net.sqlcipher.database.SQLiteQueryBuilder; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Util; import java.util.HashSet; @@ -44,7 +46,7 @@ public class MmsSmsDatabase extends Database { private static final String[] PROJECTION = {MmsSmsColumns.ID, MmsSmsColumns.UNIQUE_ROW_ID, SmsDatabase.BODY, SmsDatabase.TYPE, MmsSmsColumns.THREAD_ID, - SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, + SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, @@ -77,7 +79,7 @@ public class MmsSmsDatabase extends Database { super(context, databaseHelper); } - public @Nullable MessageRecord getMessageFor(long timestamp, Address author) { + public @Nullable MessageRecord getMessageFor(long timestamp, RecipientId author) { MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) { @@ -86,8 +88,8 @@ public class MmsSmsDatabase extends Database { MessageRecord messageRecord; while ((messageRecord = reader.getNext()) != null) { - if ((Util.isOwnNumber(context, author) && messageRecord.isOutgoing()) || - (!Util.isOwnNumber(context, author) && messageRecord.getIndividualRecipient().getAddress().equals(author))) + if ((Recipient.resolved(author).isLocalNumber() && messageRecord.isOutgoing()) || + (!Recipient.resolved(author).isLocalNumber() && messageRecord.getIndividualRecipient().getId().equals(author))) { return messageRecord; } @@ -164,19 +166,18 @@ public class MmsSmsDatabase extends Database { DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, false, true); } - public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull Address address) { + public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull RecipientId recipientId) { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) { - String serializedAddress = address.serialize(); - boolean isOwnNumber = Util.isOwnNumber(context, address); + try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.RECIPIENT_ID}, selection, order, null)) { + boolean isOwnNumber = Recipient.resolved(recipientId).isLocalNumber(); while (cursor != null && cursor.moveToNext()) { - boolean quoteIdMatches = cursor.getLong(0) == quoteId; - boolean addressMatches = serializedAddress.equals(cursor.getString(1)); + boolean quoteIdMatches = cursor.getLong(0) == quoteId; + boolean recipientIdMatches = recipientId.equals(RecipientId.from(cursor.getLong(1))); - if (quoteIdMatches && (addressMatches || isOwnNumber)) { + if (quoteIdMatches && (recipientIdMatches || isOwnNumber)) { return cursor.getPosition(); } } @@ -184,19 +185,18 @@ public class MmsSmsDatabase extends Database { return -1; } - public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull Address address) { + public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull RecipientId recipientId) { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.ADDRESS }, selection, order, null)) { - String serializedAddress = address.serialize(); - boolean isOwnNumber = Util.isOwnNumber(context, address); + try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.RECIPIENT_ID}, selection, order, null)) { + boolean isOwnNumber = Recipient.resolved(recipientId).isLocalNumber(); while (cursor != null && cursor.moveToNext()) { - boolean timestampMatches = cursor.getLong(0) == receivedTimestamp; - boolean addressMatches = serializedAddress.equals(cursor.getString(1)); + boolean timestampMatches = cursor.getLong(0) == receivedTimestamp; + boolean recipientIdMatches = recipientId.equals(RecipientId.from(cursor.getLong(1))); - if (timestampMatches && (addressMatches || isOwnNumber)) { + if (timestampMatches && (recipientIdMatches || isOwnNumber)) { return cursor.getPosition(); } } @@ -255,7 +255,7 @@ public class MmsSmsDatabase extends Database { "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, - SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, + SmsDatabase.TYPE, SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, @@ -282,7 +282,7 @@ public class MmsSmsDatabase extends Database { + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, "NULL AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, - SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, + SmsDatabase.TYPE, SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, @@ -318,7 +318,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsSmsColumns.READ); mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID); mmsColumnsPresent.add(MmsSmsColumns.BODY); - mmsColumnsPresent.add(MmsSmsColumns.ADDRESS); + mmsColumnsPresent.add(MmsSmsColumns.RECIPIENT_ID); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); mmsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT); mmsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT); @@ -375,7 +375,7 @@ public class MmsSmsDatabase extends Database { Set smsColumnsPresent = new HashSet<>(); smsColumnsPresent.add(MmsSmsColumns.ID); smsColumnsPresent.add(MmsSmsColumns.BODY); - smsColumnsPresent.add(MmsSmsColumns.ADDRESS); + smsColumnsPresent.add(MmsSmsColumns.RECIPIENT_ID); smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); smsColumnsPresent.add(MmsSmsColumns.READ); smsColumnsPresent.add(MmsSmsColumns.THREAD_ID); diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java index 7cac063b36..574115e53d 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -32,40 +33,53 @@ public class RecipientDatabase extends Database { private static final String TAG = RecipientDatabase.class.getSimpleName(); - static final String TABLE_NAME = "recipient_preferences"; - private static final String ID = "_id"; - static final String ADDRESS = "recipient_ids"; - private static final String BLOCK = "block"; - private static final String NOTIFICATION = "notification"; - private static final String VIBRATE = "vibrate"; + static final String TABLE_NAME = "recipient"; + public static final String ID = "_id"; + private static final String UUID = "uuid"; + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String GROUP_ID = "group_id"; + private static final String BLOCKED = "blocked"; + private static final String MESSAGE_RINGTONE = "message_ringtone"; + private static final String MESSAGE_VIBRATE = "message_vibrate"; + private static final String CALL_RINGTONE = "call_ringtone"; + private static final String CALL_VIBRATE = "call_vibrate"; + private static final String NOTIFICATION_CHANNEL = "notification_channel"; private static final String MUTE_UNTIL = "mute_until"; private static final String COLOR = "color"; private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder"; private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"; - private static final String EXPIRE_MESSAGES = "expire_messages"; + private static final String MESSAGE_EXPIRATION_TIME = "message_expiration_time"; static final String REGISTERED = "registered"; - private static final String PROFILE_KEY = "profile_key"; private static final String SYSTEM_DISPLAY_NAME = "system_display_name"; - private static final String SYSTEM_PHOTO_URI = "system_contact_photo"; + private static final String SYSTEM_PHOTO_URI = "system_photo_uri"; private static final String SYSTEM_PHONE_LABEL = "system_phone_label"; private static final String SYSTEM_CONTACT_URI = "system_contact_uri"; + private static final String PROFILE_KEY = "profile_key"; private static final String SIGNAL_PROFILE_NAME = "signal_profile_name"; private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar"; - private static final String PROFILE_SHARING = "profile_sharing_approval"; - private static final String CALL_RINGTONE = "call_ringtone"; - private static final String CALL_VIBRATE = "call_vibrate"; - private static final String NOTIFICATION_CHANNEL = "notification_channel"; + private static final String PROFILE_SHARING = "profile_sharing"; private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String FORCE_SMS_SELECTION = "force_sms_selection"; private static final String[] RECIPIENT_PROJECTION = new String[] { - BLOCK, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED, + UUID, PHONE, EMAIL, GROUP_ID, + BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI, SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, FORCE_SMS_SELECTION, }; + private static final String[] ID_PROJECTION = new String[] { ID }; + + private static Address addressFromCursor(Cursor cursor) { + String phone = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)); + String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL)); + String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)); + return phone != null ? Address.fromSerialized(phone) : email != null ? Address.fromSerialized(email) : Address.fromSerialized(groupId); + } + static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) .map(columnName -> TABLE_NAME + "." + columnName) .toList(); @@ -125,40 +139,105 @@ public class RecipientDatabase extends Database { } public static final String CREATE_TABLE = - "CREATE TABLE " + TABLE_NAME + - " (" + ID + " INTEGER PRIMARY KEY, " + - ADDRESS + " TEXT UNIQUE, " + - BLOCK + " INTEGER DEFAULT 0," + - NOTIFICATION + " TEXT DEFAULT NULL, " + - VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + - MUTE_UNTIL + " INTEGER DEFAULT 0, " + - COLOR + " TEXT DEFAULT NULL, " + - SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0, " + - DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + - EXPIRE_MESSAGES + " INTEGER DEFAULT 0, " + - REGISTERED + " INTEGER DEFAULT 0, " + - SYSTEM_DISPLAY_NAME + " TEXT DEFAULT NULL, " + - SYSTEM_PHOTO_URI + " TEXT DEFAULT NULL, " + - SYSTEM_PHONE_LABEL + " TEXT DEFAULT NULL, " + - SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " + - PROFILE_KEY + " TEXT DEFAULT NULL, " + - SIGNAL_PROFILE_NAME + " TEXT DEFAULT NULL, " + - SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " + - PROFILE_SHARING + " INTEGER DEFAULT 0, " + - CALL_RINGTONE + " TEXT DEFAULT NULL, " + - CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + - NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " + - UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + - FORCE_SMS_SELECTION + " INTEGER DEFAULT 0);"; + "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + UUID + " TEXT UNIQUE DEFAULT NULL, " + + PHONE + " TEXT UNIQUE DEFAULT NULL, " + + EMAIL + " TEXT UNIQUE DEFAULT NULL, " + + GROUP_ID + " TEXT UNIQUE DEFAULT NULL, " + + BLOCKED + " INTEGER DEFAULT 0," + + MESSAGE_RINGTONE + " TEXT DEFAULT NULL, " + + MESSAGE_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + + CALL_RINGTONE + " TEXT DEFAULT NULL, " + + CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + + NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " + + MUTE_UNTIL + " INTEGER DEFAULT 0, " + + COLOR + " TEXT DEFAULT NULL, " + + SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0, " + + DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + + MESSAGE_EXPIRATION_TIME + " INTEGER DEFAULT 0, " + + REGISTERED + " INTEGER DEFAULT " + RegisteredState.UNKNOWN.getId() + ", " + + SYSTEM_DISPLAY_NAME + " TEXT DEFAULT NULL, " + + SYSTEM_PHOTO_URI + " TEXT DEFAULT NULL, " + + SYSTEM_PHONE_LABEL + " TEXT DEFAULT NULL, " + + SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " + + PROFILE_KEY + " TEXT DEFAULT NULL, " + + SIGNAL_PROFILE_NAME + " TEXT DEFAULT NULL, " + + SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " + + PROFILE_SHARING + " INTEGER DEFAULT 0, " + + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + + FORCE_SMS_SELECTION + " INTEGER DEFAULT 0);"; public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } + public RecipientId getOrInsertFromE164(@NonNull String e164) { + if (TextUtils.isEmpty(e164)) { + throw new AssertionError("Phone number cannot be empty."); + } + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + String query = PHONE + " = ?"; + String[] args = new String[] { e164 }; + + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, query, args, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return RecipientId.from(cursor.getLong(0)); + } else { + ContentValues values = new ContentValues(); + values.put(PHONE, e164); + long id = db.insert(TABLE_NAME, null, values); + return RecipientId.from(id); + } + } + } + + public RecipientId getOrInsertFromEmail(@NonNull String email) { + if (TextUtils.isEmpty(email)) { + throw new AssertionError("Email cannot be empty."); + } + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + String query = EMAIL + " = ?"; + String[] args = new String[] { email }; + + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, query, args, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return RecipientId.from(cursor.getLong(0)); + } else { + ContentValues values = new ContentValues(); + values.put(EMAIL, email); + long id = db.insert(TABLE_NAME, null, values); + return RecipientId.from(id); + } + } + } + + public RecipientId getOrInsertFromGroupId(@NonNull String groupId) { + if (TextUtils.isEmpty(groupId)) { + throw new AssertionError("GroupId cannot be empty."); + } + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + String query = GROUP_ID + " = ?"; + String[] args = new String[] { groupId }; + + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, query, args, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return RecipientId.from(cursor.getLong(0)); + } else { + ContentValues values = new ContentValues(); + values.put(GROUP_ID, groupId); + long id = db.insert(TABLE_NAME, null, values); + return RecipientId.from(id); + } + } + } + public Cursor getBlocked() { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - return database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1", + return database.query(TABLE_NAME, ID_PROJECTION, BLOCKED + " = 1", null, null, null, null, null); } @@ -168,40 +247,40 @@ public class RecipientDatabase extends Database { public RecipientReader getRecipientsWithNotificationChannels() { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, NOTIFICATION_CHANNEL + " NOT NULL", + Cursor cursor = database.query(TABLE_NAME, ID_PROJECTION, NOTIFICATION_CHANNEL + " NOT NULL", null, null, null, null, null); return new RecipientReader(context, cursor); } - public Optional getRecipientSettings(@NonNull Address address) { + public @NonNull RecipientSettings getRecipientSettings(@NonNull RecipientId id) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - Cursor cursor = null; - - try { - cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null); + String query = ID + " = ?"; + String[] args = new String[] { id.serialize() }; + try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) { if (cursor != null && cursor.moveToNext()) { return getRecipientSettings(cursor); + } else { + // TODO: Maybe not make this an error? + throw new AssertionError("Couldn't find recipient!"); } - - return Optional.absent(); - } finally { - if (cursor != null) cursor.close(); } } - Optional getRecipientSettings(@NonNull Cursor cursor) { - boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; - String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION)); + @NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { + long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); + Address address = addressFromCursor(cursor); + boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1; + String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE)); String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); - int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); + int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_VIBRATE)); int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1; int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); - int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); + int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_EXPIRATION_TIME)); int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); @@ -234,18 +313,18 @@ public class RecipientDatabase extends Database { } } - return Optional.of(new RecipientSettings(blocked, muteUntil, - VibrateState.fromId(messageVibrateState), - VibrateState.fromId(callVibrateState), - Util.uri(messageRingtone), Util.uri(callRingtone), - color, seenInviteReminder, - defaultSubscriptionId, expireMessages, - RegisteredState.fromId(registeredState), - profileKey, systemDisplayName, systemContactPhoto, - systemPhoneLabel, systemContactUri, - signalProfileName, signalProfileAvatar, profileSharing, - notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), - forceSmsSelection)); + return new RecipientSettings(RecipientId.from(id), address, blocked, muteUntil, + VibrateState.fromId(messageVibrateState), + VibrateState.fromId(callVibrateState), + Util.uri(messageRingtone), Util.uri(callRingtone), + color, seenInviteReminder, + defaultSubscriptionId, expireMessages, + RegisteredState.fromId(registeredState), + profileKey, systemDisplayName, systemContactPhoto, + systemPhoneLabel, systemContactUri, + signalProfileName, signalProfileAvatar, profileSharing, + notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), + forceSmsSelection); } public BulkOperationsHandle resetAllSystemContactInfo() { @@ -263,187 +342,185 @@ public class RecipientDatabase extends Database { return new BulkOperationsHandle(database); } - public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) { + public void setColor(@NonNull RecipientId id, @NonNull MaterialColor color) { ContentValues values = new ContentValues(); values.put(COLOR, color.serialize()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setColor(color); + update(id, values); + Recipient.live(id).refresh(); } - public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) { + public void setDefaultSubscriptionId(@NonNull RecipientId id, int defaultSubscriptionId) { ContentValues values = new ContentValues(); values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId)); + update(id, values); + Recipient.live(id).refresh(); } - public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) { + public void setForceSmsSelection(@NonNull RecipientId id, boolean forceSmsSelection) { ContentValues contentValues = new ContentValues(1); contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setForceSmsSelection(forceSmsSelection); + update(id, contentValues); + Recipient.live(id).refresh(); } - public void setBlocked(@NonNull Recipient recipient, boolean blocked) { + public void setBlocked(@NonNull RecipientId id, boolean blocked) { ContentValues values = new ContentValues(); - values.put(BLOCK, blocked ? 1 : 0); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setBlocked(blocked); + values.put(BLOCKED, blocked ? 1 : 0); + update(id, values); + Recipient.live(id).refresh(); } - public void setMessageRingtone(@NonNull Recipient recipient, @Nullable Uri notification) { + public void setMessageRingtone(@NonNull RecipientId id, @Nullable Uri notification) { ContentValues values = new ContentValues(); - values.put(NOTIFICATION, notification == null ? null : notification.toString()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setMessageRingtone(notification); + values.put(MESSAGE_RINGTONE, notification == null ? null : notification.toString()); + update(id, values); + Recipient.live(id).refresh(); } - public void setCallRingtone(@NonNull Recipient recipient, @Nullable Uri ringtone) { + public void setCallRingtone(@NonNull RecipientId id, @Nullable Uri ringtone) { ContentValues values = new ContentValues(); values.put(CALL_RINGTONE, ringtone == null ? null : ringtone.toString()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setCallRingtone(ringtone); + update(id, values); + Recipient.live(id).refresh(); } - public void setMessageVibrate(@NonNull Recipient recipient, @NonNull VibrateState enabled) { + public void setMessageVibrate(@NonNull RecipientId id, @NonNull VibrateState enabled) { ContentValues values = new ContentValues(); - values.put(VIBRATE, enabled.getId()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setMessageVibrate(enabled); + values.put(MESSAGE_VIBRATE, enabled.getId()); + update(id, values); + Recipient.live(id).refresh(); } - public void setCallVibrate(@NonNull Recipient recipient, @NonNull VibrateState enabled) { + public void setCallVibrate(@NonNull RecipientId id, @NonNull VibrateState enabled) { ContentValues values = new ContentValues(); values.put(CALL_VIBRATE, enabled.getId()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setCallVibrate(enabled); + update(id, values); + Recipient.live(id).refresh(); } - public void setMuted(@NonNull Recipient recipient, long until) { + public void setMuted(@NonNull RecipientId id, long until) { ContentValues values = new ContentValues(); values.put(MUTE_UNTIL, until); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setMuted(until); + update(id, values); + Recipient.live(id).refresh(); } - public void setSeenInviteReminder(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean seen) { + public void setSeenInviteReminder(@NonNull RecipientId id, @SuppressWarnings("SameParameterValue") boolean seen) { ContentValues values = new ContentValues(1); values.put(SEEN_INVITE_REMINDER, seen ? 1 : 0); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setHasSeenInviteReminder(seen); + update(id, values); + Recipient.live(id).refresh(); } - public void setExpireMessages(@NonNull Recipient recipient, int expiration) { - recipient.setExpireMessages(expiration); - + public void setExpireMessages(@NonNull RecipientId id, int expiration) { ContentValues values = new ContentValues(1); - values.put(EXPIRE_MESSAGES, expiration); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setExpireMessages(expiration); + values.put(MESSAGE_EXPIRATION_TIME, expiration); + update(id, values); + Recipient.live(id).refresh(); } - public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { + public void setUnidentifiedAccessMode(@NonNull RecipientId id, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { ContentValues values = new ContentValues(1); values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode); + update(id, values); + Recipient.live(id).refresh(); } - public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { + public void setProfileKey(@NonNull RecipientId id, @Nullable byte[] profileKey) { ContentValues values = new ContentValues(1); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setProfileKey(profileKey); + update(id, values); + Recipient.live(id).refresh(); } - public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) { + public void setProfileName(@NonNull RecipientId id, @Nullable String profileName) { ContentValues contentValues = new ContentValues(1); contentValues.put(SIGNAL_PROFILE_NAME, profileName); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setProfileName(profileName); + update(id, contentValues); + Recipient.live(id).refresh(); } - public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) { + public void setProfileAvatar(@NonNull RecipientId id, @Nullable String profileAvatar) { ContentValues contentValues = new ContentValues(1); contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setProfileAvatar(profileAvatar); + update(id, contentValues); + Recipient.live(id).refresh(); } - public void setProfileSharing(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean enabled) { + public void setProfileSharing(@NonNull RecipientId id, @SuppressWarnings("SameParameterValue") boolean enabled) { ContentValues contentValues = new ContentValues(1); contentValues.put(PROFILE_SHARING, enabled ? 1 : 0); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setProfileSharing(enabled); + update(id, contentValues); + Recipient.live(id).refresh(); } - public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) { + public void setNotificationChannel(@NonNull RecipientId id, @Nullable String notificationChannel) { ContentValues contentValues = new ContentValues(1); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setNotificationChannel(notificationChannel); + update(id, contentValues); + Recipient.live(id).refresh(); } public Set
getAllAddresses() { SQLiteDatabase db = databaseHelper.getReadableDatabase(); Set
results = new HashSet<>(); - try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, null, null, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID, UUID, PHONE, EMAIL, GROUP_ID }, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - results.add(Address.fromExternal(context, cursor.getString(0))); + results.add(addressFromCursor(cursor)); } } return results; } - public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) { + public void setRegistered(@NonNull RecipientId id, RegisteredState registeredState) { ContentValues contentValues = new ContentValues(1); contentValues.put(REGISTERED, registeredState.getId()); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setRegistered(registeredState); + update(id, contentValues); + Recipient.live(id).refresh(); } - public void setRegistered(@NonNull List
activeAddresses, - @NonNull List
inactiveAddresses) + public void setRegistered(@NonNull List activeIds, + @NonNull List inactiveIds) { - for (Address activeAddress : activeAddresses) { + for (RecipientId activeId : activeIds) { ContentValues contentValues = new ContentValues(1); contentValues.put(REGISTERED, RegisteredState.REGISTERED.getId()); - updateOrInsert(activeAddress, contentValues); - Recipient.applyCached(activeAddress, recipient -> recipient.setRegistered(RegisteredState.REGISTERED)); + update(activeId, contentValues); + Recipient.live(activeId).refresh(); } - for (Address inactiveAddress : inactiveAddresses) { + for (RecipientId inactiveId : inactiveIds) { ContentValues contentValues = new ContentValues(1); contentValues.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId()); - updateOrInsert(inactiveAddress, contentValues); - Recipient.applyCached(inactiveAddress, recipient -> recipient.setRegistered(RegisteredState.NOT_REGISTERED)); + update(inactiveId, contentValues); + Recipient.live(inactiveId).refresh(); } } - public List
getRegistered() { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - List
results = new LinkedList<>(); + public List getRegistered() { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + List results = new LinkedList<>(); - try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, REGISTERED + " = ?", new String[] {"1"}, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, REGISTERED + " = ?", new String[] {"1"}, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - results.add(Address.fromSerialized(cursor.getString(0))); + results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))); } } return results; } - public List
getSystemContacts() { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - List
results = new LinkedList<>(); + public List getSystemContacts() { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + List results = new LinkedList<>(); - try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - results.add(Address.fromSerialized(cursor.getString(0))); + results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))); } } @@ -451,93 +528,61 @@ public class RecipientDatabase extends Database { } public void updateSystemContactColors(@NonNull ColorUpdater updater) { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Map updates = new HashMap<>(); + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + Map updates = new HashMap<>(); db.beginTransaction(); - try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS, COLOR, SYSTEM_DISPLAY_NAME}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID, COLOR, SYSTEM_DISPLAY_NAME}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); + long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(COLOR))); ContentValues contentValues = new ContentValues(1); contentValues.put(COLOR, newColor.serialize()); - db.update(TABLE_NAME, contentValues, ADDRESS + " = ?", new String[]{address.serialize()}); + db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] { String.valueOf(id) }); - updates.put(address, newColor); + updates.put(RecipientId.from(id), newColor); } } finally { db.setTransactionSuccessful(); db.endTransaction(); - Stream.of(updates.entrySet()).forEach(entry -> { - Recipient.applyCached(entry.getKey(), recipient -> { - recipient.setColor(entry.getValue()); - }); - }); + Stream.of(updates.entrySet()).forEach(entry -> Recipient.live(entry.getKey()).refresh()); } } - // XXX This shouldn't be here, and is just a temporary workaround - public RegisteredState isRegistered(@NonNull Address address) { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - - try (Cursor cursor = db.query(TABLE_NAME, new String[] {REGISTERED}, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) return RegisteredState.fromId(cursor.getInt(0)); - else return RegisteredState.UNKNOWN; - } - } - - private void updateOrInsert(Address address, ContentValues contentValues) { + private void update(@NonNull RecipientId id, ContentValues contentValues) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); - - database.beginTransaction(); - - int updated = database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", - new String[] {address.serialize()}); - - if (updated < 1) { - contentValues.put(ADDRESS, address.serialize()); - database.insert(TABLE_NAME, null, contentValues); - } - - database.setTransactionSuccessful(); - database.endTransaction(); + database.update(TABLE_NAME, contentValues, ID + " = ?", new String[] { id.serialize() }); } public class BulkOperationsHandle { private final SQLiteDatabase database; - private final Map pendingContactInfoMap = new HashMap<>(); + private final Map pendingContactInfoMap = new HashMap<>(); BulkOperationsHandle(SQLiteDatabase database) { this.database = database; } - public void setSystemContactInfo(@NonNull Address address, @Nullable String displayName, @Nullable String photoUri, @Nullable String systemPhoneLabel, @Nullable String systemContactUri) { + public void setSystemContactInfo(@NonNull RecipientId id, @Nullable String displayName, @Nullable String photoUri, @Nullable String systemPhoneLabel, @Nullable String systemContactUri) { ContentValues contentValues = new ContentValues(1); contentValues.put(SYSTEM_DISPLAY_NAME, displayName); contentValues.put(SYSTEM_PHOTO_URI, photoUri); contentValues.put(SYSTEM_PHONE_LABEL, systemPhoneLabel); contentValues.put(SYSTEM_CONTACT_URI, systemContactUri); - updateOrInsert(address, contentValues); - pendingContactInfoMap.put(address, new PendingContactInfo(displayName, photoUri, systemPhoneLabel, systemContactUri)); + update(id, contentValues); + pendingContactInfoMap.put(id, new PendingContactInfo(displayName, photoUri, systemPhoneLabel, systemContactUri)); } public void finish() { database.setTransactionSuccessful(); database.endTransaction(); - Stream.of(pendingContactInfoMap.entrySet()) - .forEach(entry -> Recipient.applyCached(entry.getKey(), recipient -> { - recipient.setName(entry.getValue().displayName); - recipient.setSystemContactPhoto(Util.uri(entry.getValue().photoUri)); - recipient.setCustomLabel(entry.getValue().phoneLabel); - recipient.setContactUri(Util.uri(entry.getValue().contactUri)); - })); + Stream.of(pendingContactInfoMap.entrySet()).forEach(entry -> Recipient.live(entry.getKey()).refresh()); } } @@ -546,6 +591,8 @@ public class RecipientDatabase extends Database { } public static class RecipientSettings { + private final RecipientId id; + private final Address address; private final boolean blocked; private final long muteUntil; private final VibrateState messageVibrateState; @@ -569,7 +616,8 @@ public class RecipientDatabase extends Database { private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; - RecipientSettings(boolean blocked, long muteUntil, + RecipientSettings(@NonNull RecipientId id, + @NonNull Address address, boolean blocked, long muteUntil, @NonNull VibrateState messageVibrateState, @NonNull VibrateState callVibrateState, @Nullable Uri messageRingtone, @@ -591,6 +639,8 @@ public class RecipientDatabase extends Database { @NonNull UnidentifiedAccessMode unidentifiedAccessMode, boolean forceSmsSelection) { + this.id = id; + this.address = address; this.blocked = blocked; this.muteUntil = muteUntil; this.messageVibrateState = messageVibrateState; @@ -615,6 +665,14 @@ public class RecipientDatabase extends Database { this.forceSmsSelection = forceSmsSelection; } + public RecipientId getId() { + return id; + } + + public @NonNull Address getAddress() { + return address; + } + public @Nullable MaterialColor getColor() { return color; } @@ -715,8 +773,8 @@ public class RecipientDatabase extends Database { } public @NonNull Recipient getCurrent() { - String serialized = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); - return Recipient.from(context, Address.fromSerialized(serialized), false); + RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID))); + return Recipient.live(id).get(); } public @Nullable Recipient getNext() { diff --git a/src/org/thoughtcrime/securesms/database/SearchDatabase.java b/src/org/thoughtcrime/securesms/database/SearchDatabase.java index 329f4d8cfd..5a46fa09a6 100644 --- a/src/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -20,12 +20,12 @@ public class SearchDatabase extends Database { public static final String SMS_FTS_TABLE_NAME = "sms_fts"; public static final String MMS_FTS_TABLE_NAME = "mms_fts"; - public static final String ID = "rowid"; - public static final String BODY = MmsSmsColumns.BODY; - public static final String THREAD_ID = MmsSmsColumns.THREAD_ID; - public static final String SNIPPET = "snippet"; - public static final String CONVERSATION_ADDRESS = "conversation_address"; - public static final String MESSAGE_ADDRESS = "message_address"; + public static final String ID = "rowid"; + public static final String BODY = MmsSmsColumns.BODY; + public static final String THREAD_ID = MmsSmsColumns.THREAD_ID; + public static final String SNIPPET = "snippet"; + public static final String CONVERSATION_RECIPIENT = "conversation_recipient"; + public static final String MESSAGE_RECIPIENT = "message_recipient"; public static final String[] CREATE_TABLE = { "CREATE VIRTUAL TABLE " + SMS_FTS_TABLE_NAME + " USING fts5(" + BODY + ", " + THREAD_ID + " UNINDEXED, content=" + SmsDatabase.TABLE_NAME + ", content_rowid=" + SmsDatabase.ID + ");", @@ -58,8 +58,8 @@ public class SearchDatabase extends Database { private static final String MESSAGES_QUERY = "SELECT " + - ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + - MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " + + MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " + "snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + @@ -69,8 +69,8 @@ public class SearchDatabase extends Database { "WHERE " + SMS_FTS_TABLE_NAME + " MATCH ? " + "UNION ALL " + "SELECT " + - ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + - MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " + + MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " + "snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + @@ -83,8 +83,8 @@ public class SearchDatabase extends Database { private static final String MESSAGES_FOR_THREAD_QUERY = "SELECT " + - ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + - MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " + + MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " + "snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + @@ -94,8 +94,8 @@ public class SearchDatabase extends Database { "WHERE " + SMS_FTS_TABLE_NAME + " MATCH ? AND " + SmsDatabase.TABLE_NAME + "." + MmsSmsColumns.THREAD_ID + " = ? " + "UNION ALL " + "SELECT " + - ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + - MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " + + MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " + "snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + diff --git a/src/org/thoughtcrime/securesms/database/SessionDatabase.java b/src/org/thoughtcrime/securesms/database/SessionDatabase.java index 1730249988..a25096f332 100644 --- a/src/org/thoughtcrime/securesms/database/SessionDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SessionDatabase.java @@ -11,6 +11,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -24,37 +25,37 @@ public class SessionDatabase extends Database { public static final String TABLE_NAME = "sessions"; - private static final String ID = "_id"; - public static final String ADDRESS = "address"; - public static final String DEVICE = "device"; - public static final String RECORD = "record"; + private static final String ID = "_id"; + public static final String RECIPIENT_ID = "address"; + public static final String DEVICE = "device"; + public static final String RECORD = "record"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + - "(" + ID + " INTEGER PRIMARY KEY, " + ADDRESS + " TEXT NOT NULL, " + + "(" + ID + " INTEGER PRIMARY KEY, " + RECIPIENT_ID + " INTEGER NOT NULL, " + DEVICE + " INTEGER NOT NULL, " + RECORD + " BLOB NOT NULL, " + - "UNIQUE(" + ADDRESS + "," + DEVICE + ") ON CONFLICT REPLACE);"; + "UNIQUE(" + RECIPIENT_ID + "," + DEVICE + ") ON CONFLICT REPLACE);"; SessionDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } - public void store(@NonNull Address address, int deviceId, @NonNull SessionRecord record) { + public void store(@NonNull RecipientId recipientId, int deviceId, @NonNull SessionRecord record) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(ADDRESS, address.serialize()); + values.put(RECIPIENT_ID, recipientId.serialize()); values.put(DEVICE, deviceId); values.put(RECORD, record.serialize()); database.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); } - public @Nullable SessionRecord load(@NonNull Address address, int deviceId) { + public @Nullable SessionRecord load(@NonNull RecipientId recipientId, int deviceId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); try (Cursor cursor = database.query(TABLE_NAME, new String[]{RECORD}, - ADDRESS + " = ? AND " + DEVICE + " = ?", - new String[] {address.serialize(), String.valueOf(deviceId)}, + RECIPIENT_ID + " = ? AND " + DEVICE + " = ?", + new String[] {recipientId.serialize(), String.valueOf(deviceId)}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { @@ -69,18 +70,18 @@ public class SessionDatabase extends Database { return null; } - public @NonNull List getAllFor(@NonNull Address address) { + public @NonNull List getAllFor(@NonNull RecipientId recipientId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); List results = new LinkedList<>(); try (Cursor cursor = database.query(TABLE_NAME, null, - ADDRESS + " = ?", - new String[] {address.serialize()}, + RECIPIENT_ID + " = ?", + new String[] {recipientId.serialize()}, null, null, null)) { while (cursor != null && cursor.moveToNext()) { try { - results.add(new SessionRow(address, + results.add(new SessionRow(recipientId, cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)), new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD))))); } catch (IOException e) { @@ -99,7 +100,7 @@ public class SessionDatabase extends Database { try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { try { - results.add(new SessionRow(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))), + results.add(new SessionRow(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))), cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)), new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD))))); } catch (IOException e) { @@ -111,13 +112,13 @@ public class SessionDatabase extends Database { return results; } - public @NonNull List getSubDevices(@NonNull Address address) { + public @NonNull List getSubDevices(@NonNull RecipientId recipientId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); List results = new LinkedList<>(); try (Cursor cursor = database.query(TABLE_NAME, new String[] {DEVICE}, - ADDRESS + " = ?", - new String[] {address.serialize()}, + RECIPIENT_ID + " = ?", + new String[] {recipientId.serialize()}, null, null, null)) { while (cursor != null && cursor.moveToNext()) { @@ -132,31 +133,31 @@ public class SessionDatabase extends Database { return results; } - public void delete(@NonNull Address address, int deviceId) { + public void delete(@NonNull RecipientId recipientId, int deviceId) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); - database.delete(TABLE_NAME, ADDRESS + " = ? AND " + DEVICE + " = ?", - new String[] {address.serialize(), String.valueOf(deviceId)}); + database.delete(TABLE_NAME, RECIPIENT_ID + " = ? AND " + DEVICE + " = ?", + new String[] {recipientId.serialize(), String.valueOf(deviceId)}); } - public void deleteAllFor(@NonNull Address address) { + public void deleteAllFor(@NonNull RecipientId recipientId) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); - database.delete(TABLE_NAME, ADDRESS + " = ?", new String[] {address.serialize()}); + database.delete(TABLE_NAME, RECIPIENT_ID + " = ?", new String[] {recipientId.serialize()}); } public static final class SessionRow { - private final Address address; + private final RecipientId recipientId; private final int deviceId; private final SessionRecord record; - public SessionRow(Address address, int deviceId, SessionRecord record) { - this.address = address; - this.deviceId = deviceId; - this.record = record; + public SessionRow(@NonNull RecipientId recipientId, int deviceId, SessionRecord record) { + this.recipientId = recipientId; + this.deviceId = deviceId; + this.record = record; } - public Address getAddress() { - return address; + public RecipientId getRecipientId() { + return recipientId; } public int getDeviceId() { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 81527845b5..bfcc77c9e8 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; @@ -73,7 +74,7 @@ public class SmsDatabase extends MessagingDatabase { public static final String SERVICE_CENTER = "service_center"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " integer PRIMARY KEY, " + - THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " + + THREAD_ID + " INTEGER, " + RECIPIENT_ID + " INTEGER, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + @@ -91,7 +92,7 @@ public class SmsDatabase extends MessagingDatabase { }; private static final String[] MESSAGE_PROJECTION = new String[] { - ID, THREAD_ID, ADDRESS, ADDRESS_DEVICE_ID, PERSON, + ID, THREAD_ID, RECIPIENT_ID, ADDRESS_DEVICE_ID, PERSON, DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED, DATE_SENT + " AS " + NORMALIZED_DATE_SENT, PROTOCOL, READ, STATUS, TYPE, @@ -100,8 +101,8 @@ public class SmsDatabase extends MessagingDatabase { NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED }; - private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); - private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); + private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache("SmsDelivery"); + private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache("SmsRead"); public SmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -310,17 +311,17 @@ public class SmsDatabase extends MessagingDatabase { boolean foundMessage = false; try { - cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS, TYPE}, + cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); while (cursor.moveToNext()) { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) { - Address theirAddress = messageId.getAddress(); - Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; + RecipientId theirRecipientId = messageId.getRecipientId(); + RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))); + String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; - if (ourAddress.equals(theirAddress)) { + if (outRecipientId.equals(theirRecipientId)) { long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); database.execSQL("UPDATE " + TABLE_NAME + @@ -336,8 +337,8 @@ public class SmsDatabase extends MessagingDatabase { } if (!foundMessage) { - if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); - if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId()); + if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId()); } } finally { @@ -352,15 +353,15 @@ public class SmsDatabase extends MessagingDatabase { Cursor cursor = null; try { - cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS, TYPE, EXPIRES_IN, EXPIRE_STARTED}, + cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE, EXPIRES_IN, EXPIRE_STARTED}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null); while (cursor.moveToNext()) { - Address theirAddress = messageId.getAddress(); - Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); + RecipientId theirRecipientId = messageId.getRecipientId(); + RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))); - if (ourAddress.equals(theirAddress)) { + if (outRecipientId.equals(theirRecipientId)) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); @@ -405,11 +406,11 @@ public class SmsDatabase extends MessagingDatabase { database.beginTransaction(); try { - cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS, DATE_SENT, TYPE, EXPIRES_IN, EXPIRE_STARTED}, where, arguments, null, null, null); + cursor = database.query(TABLE_NAME, new String[] {ID, RECIPIENT_ID, DATE_SENT, TYPE, EXPIRES_IN, EXPIRE_STARTED}, where, arguments, null, null, null); while (cursor != null && cursor.moveToNext()) { if (Types.isSecureType(cursor.getLong(3))) { - SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(cursor.getString(1)), cursor.getLong(2)); + SyncMessageId syncMessageId = new SyncMessageId(RecipientId.from(cursor.getLong(1)), cursor.getLong(2)); ExpirationInfo expirationInfo = new ExpirationInfo(cursor.getLong(0), cursor.getLong(4), cursor.getLong(5), false); results.add(new MarkedMessageInfo(syncMessageId, expirationInfo)); @@ -461,7 +462,7 @@ public class SmsDatabase extends MessagingDatabase { ContentValues contentValues = new ContentValues(); contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE); - contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize()); + contentValues.put(RECIPIENT_ID, record.getIndividualRecipient().getId().serialize()); contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId()); contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); contentValues.put(DATE_SENT, record.getDateSent()); @@ -485,24 +486,24 @@ public class SmsDatabase extends MessagingDatabase { } } - public @NonNull Pair insertReceivedCall(@NonNull Address address) { + public @NonNull Pair insertReceivedCall(@NonNull RecipientId address) { return insertCallLog(address, Types.INCOMING_CALL_TYPE, false); } - public @NonNull Pair insertOutgoingCall(@NonNull Address address) { + public @NonNull Pair insertOutgoingCall(@NonNull RecipientId address) { return insertCallLog(address, Types.OUTGOING_CALL_TYPE, false); } - public @NonNull Pair insertMissedCall(@NonNull Address address) { + public @NonNull Pair insertMissedCall(@NonNull RecipientId address) { return insertCallLog(address, Types.MISSED_CALL_TYPE, true); } - private @NonNull Pair insertCallLog(@NonNull Address address, long type, boolean unread) { - Recipient recipient = Recipient.from(context, address, true); + private @NonNull Pair insertCallLog(@NonNull RecipientId recipientId, long type, boolean unread) { + Recipient recipient = Recipient.resolved(recipientId); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); ContentValues values = new ContentValues(6); - values.put(ADDRESS, address.serialize()); + values.put(RECIPIENT_ID, recipientId.serialize()); values.put(ADDRESS_DEVICE_ID, 1); values.put(DATE_RECEIVED, System.currentTimeMillis()); values.put(DATE_SENT, System.currentTimeMillis()); @@ -547,14 +548,15 @@ public class SmsDatabase extends MessagingDatabase { if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT; else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT; - Recipient recipient = Recipient.from(context, message.getSender(), true); + Recipient recipient = Recipient.resolved(message.getSender()); Recipient groupRecipient; if (message.getGroupId() == null) { groupRecipient = null; } else { - groupRecipient = Recipient.from(context, message.getGroupId(), true); + RecipientId id = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(message.getGroupId().serialize()); + groupRecipient = Recipient.resolved(id); } boolean unread = (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) || @@ -567,7 +569,7 @@ public class SmsDatabase extends MessagingDatabase { else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); ContentValues values = new ContentValues(6); - values.put(ADDRESS, message.getSender().serialize()); + values.put(RECIPIENT_ID, message.getSender().serialize()); values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId()); values.put(DATE_RECEIVED, System.currentTimeMillis()); values.put(DATE_SENT, message.getSentTimestampMillis()); @@ -602,7 +604,7 @@ public class SmsDatabase extends MessagingDatabase { } if (message.getSubscriptionId() != -1) { - DatabaseFactory.getRecipientDatabase(context).setDefaultSubscriptionId(recipient, message.getSubscriptionId()); + DatabaseFactory.getRecipientDatabase(context).setDefaultSubscriptionId(recipient.getId(), message.getSubscriptionId()); } notifyConversationListeners(threadId); @@ -632,12 +634,12 @@ public class SmsDatabase extends MessagingDatabase { if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT; else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT; - Address address = message.getRecipient().getAddress(); - Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date); - Map earlyReadReceipts = earlyReadReceiptCache.remove(date); + RecipientId recipientId = message.getRecipient().getId(); + Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date); + Map earlyReadReceipts = earlyReadReceiptCache.remove(date); ContentValues contentValues = new ContentValues(6); - contentValues.put(ADDRESS, address.serialize()); + contentValues.put(RECIPIENT_ID, recipientId.serialize()); contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); @@ -650,7 +652,7 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); SQLiteDatabase db = databaseHelper.getWritableDatabase(); - long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); + long messageId = db.insert(TABLE_NAME, null, contentValues); if (insertListener != null) { insertListener.onComplete(); @@ -724,7 +726,7 @@ public class SmsDatabase extends MessagingDatabase { private boolean isDuplicate(IncomingTextMessage message, long threadId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?", + Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + RECIPIENT_ID + " = ? AND " + THREAD_ID + " = ?", new String[]{String.valueOf(message.getSentTimestampMillis()), message.getSender().serialize(), String.valueOf(threadId)}, null, null, null, "1"); @@ -783,19 +785,19 @@ public class SmsDatabase extends MessagingDatabase { } /*package*/ SQLiteStatement createInsertStatement(SQLiteDatabase database) { - return database.compileStatement("INSERT INTO " + TABLE_NAME + " (" + ADDRESS + ", " + - PERSON + ", " + - DATE_SENT + ", " + - DATE_RECEIVED + ", " + - PROTOCOL + ", " + - READ + ", " + - STATUS + ", " + - TYPE + ", " + - REPLY_PATH_PRESENT + ", " + - SUBJECT + ", " + - BODY + ", " + - SERVICE_CENTER + - ", " + THREAD_ID + ") " + + return database.compileStatement("INSERT INTO " + TABLE_NAME + " (" + RECIPIENT_ID + ", " + + PERSON + ", " + + DATE_SENT + ", " + + DATE_RECEIVED + ", " + + PROTOCOL + ", " + + READ + ", " + + STATUS + ", " + + TYPE + ", " + + REPLY_PATH_PRESENT + ", " + + SUBJECT + ", " + + BODY + ", " + + SERVICE_CENTER + + ", " + THREAD_ID + ") " + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); } @@ -859,7 +861,7 @@ public class SmsDatabase extends MessagingDatabase { public SmsMessageRecord getCurrent() { long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS))); + long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.RECIPIENT_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED)); @@ -880,7 +882,7 @@ public class SmsDatabase extends MessagingDatabase { } List mismatches = getMismatches(mismatchDocument); - Recipient recipient = Recipient.from(context, address, true); + Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get(); return new SmsMessageRecord(messageId, body, recipient, recipient, diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index 33733f03c4..7b68c7eb29 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -22,15 +22,18 @@ import android.database.sqlite.SQLiteException; import android.net.Uri; import androidx.annotation.Nullable; +import com.annimon.stream.Stream; + import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; @@ -39,6 +42,20 @@ public class SmsMigrator { private static final String TAG = SmsMigrator.class.getSimpleName(); + private static class SystemColumns { + private static final String ADDRESS = "address"; + private static final String PERSON = "person"; + private static final String DATE_RECEIVED = "date"; + private static final String PROTOCOL = "protocol"; + private static final String READ = "read"; + private static final String STATUS = "status"; + private static final String TYPE = "type"; + private static final String SUBJECT = "subject"; + private static final String REPLY_PATH_PRESENT = "reply_path_present"; + private static final String BODY = "body"; + private static final String SERVICE_CENTER = "service_center"; + } + private static void addStringToStatement(SQLiteStatement statement, Cursor cursor, int index, String key) { @@ -85,23 +102,22 @@ public class SmsMigrator { ourType == MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE; } - private static void getContentValuesForRow(Context context, Cursor cursor, - long threadId, SQLiteStatement statement) - { - String theirAddress = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)); - statement.bindString(1, Address.fromExternal(context, theirAddress).serialize()); + private static void getContentValuesForRow(Context context, Cursor cursor, long threadId, SQLiteStatement statement) { + String address = cursor.getString(cursor.getColumnIndexOrThrow(SystemColumns.ADDRESS)); + RecipientId id = Recipient.external(context, address).getId(); - addIntToStatement(statement, cursor, 2, SmsDatabase.PERSON); - addIntToStatement(statement, cursor, 3, SmsDatabase.DATE_RECEIVED); - addIntToStatement(statement, cursor, 4, SmsDatabase.DATE_RECEIVED); - addIntToStatement(statement, cursor, 5, SmsDatabase.PROTOCOL); - addIntToStatement(statement, cursor, 6, SmsDatabase.READ); - addIntToStatement(statement, cursor, 7, SmsDatabase.STATUS); - addTranslatedTypeToStatement(statement, cursor, 8, SmsDatabase.TYPE); - addIntToStatement(statement, cursor, 9, SmsDatabase.REPLY_PATH_PRESENT); - addStringToStatement(statement, cursor, 10, SmsDatabase.SUBJECT); - addStringToStatement(statement, cursor, 11, SmsDatabase.BODY); - addStringToStatement(statement, cursor, 12, SmsDatabase.SERVICE_CENTER); + statement.bindString(1, id.serialize()); + addIntToStatement(statement, cursor, 2, SystemColumns.PERSON); + addIntToStatement(statement, cursor, 3, SystemColumns.DATE_RECEIVED); + addIntToStatement(statement, cursor, 4, SystemColumns.DATE_RECEIVED); + addIntToStatement(statement, cursor, 5, SystemColumns.PROTOCOL); + addIntToStatement(statement, cursor, 6, SystemColumns.READ); + addIntToStatement(statement, cursor, 7, SystemColumns.STATUS); + addTranslatedTypeToStatement(statement, cursor, 8, SystemColumns.TYPE); + addIntToStatement(statement, cursor, 9, SystemColumns.REPLY_PATH_PRESENT); + addStringToStatement(statement, cursor, 10, SystemColumns.SUBJECT); + addStringToStatement(statement, cursor, 11, SystemColumns.BODY); + addStringToStatement(statement, cursor, 12, SystemColumns.SERVICE_CENTER); statement.bindLong(13, threadId); } @@ -136,7 +152,7 @@ public class SmsMigrator { String address = getTheirCanonicalAddress(context, theirRecipientId); if (address != null) { - recipientList.add(Recipient.from(context, Address.fromExternal(context, address), true)); + recipientList.add(Recipient.external(context, address)); } } @@ -212,17 +228,14 @@ public class SmsMigrator { long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients.iterator().next()); migrateConversation(context, listener, progress, theirThreadId, ourThreadId); } else if (ourRecipients.size() > 1) { - ourRecipients.add(Recipient.from(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), true)); + ourRecipients.add(Recipient.self()); - List
memberAddresses = new LinkedList<>(); + List recipientIds = Stream.of(ourRecipients).map(Recipient::getId).toList(); - for (Recipient recipient : ourRecipients) { - memberAddresses.add(recipient.getAddress()); - } - - String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(memberAddresses, true); - Recipient ourGroupRecipient = Recipient.from(context, Address.fromSerialized(ourGroupId), true); - long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); + String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipientIds, true); + RecipientId ourGroupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(ourGroupId); + Recipient ourGroupRecipient = Recipient.resolved(ourGroupRecipientId); + long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); migrateConversation(context, listener, progress, theirThreadId, ourThreadId); } diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index bd24659770..4ddc8445b6 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -32,9 +32,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.ContactUtil; -import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -44,12 +42,11 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.libsignal.util.guava.Optional; import java.io.Closeable; import java.io.IOException; @@ -65,7 +62,7 @@ public class ThreadDatabase extends Database { public static final String ID = "_id"; public static final String DATE = "date"; public static final String MESSAGE_COUNT = "message_count"; - public static final String ADDRESS = "recipient_ids"; + public static final String RECIPIENT_ID = "recipient_ids"; public static final String SNIPPET = "snippet"; private static final String SNIPPET_CHARSET = "snippet_cs"; public static final String READ = "read"; @@ -86,7 +83,7 @@ public class ThreadDatabase extends Database { public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + - MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " + + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + RECIPIENT_ID + " INTEGER, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + @@ -97,12 +94,12 @@ public class ThreadDatabase extends Database { READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { - "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");", + "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");", "CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");", }; private static final String[] THREAD_PROJECTION = { - ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE, + ID, DATE, MESSAGE_COUNT, RECIPIENT_ID, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE, SNIPPET_URI, SNIPPET_CONTENT_TYPE, SNIPPET_EXTRAS, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT }; @@ -119,12 +116,12 @@ public class ThreadDatabase extends Database { super(context, databaseHelper); } - private long createThreadForRecipient(Address address, boolean group, int distributionType) { + private long createThreadForRecipient(@NonNull RecipientId recipientId, boolean group, int distributionType) { ContentValues contentValues = new ContentValues(4); long date = System.currentTimeMillis(); contentValues.put(DATE, date - date % 1000); - contentValues.put(ADDRESS, address.serialize()); + contentValues.put(RECIPIENT_ID, recipientId.serialize()); if (group) contentValues.put(TYPE, distributionType); @@ -340,24 +337,24 @@ public class ThreadDatabase extends Database { } - public Cursor getFilteredConversationList(@Nullable List
filter) { + public Cursor getFilteredConversationList(@Nullable List filter) { if (filter == null || filter.size() == 0) return null; - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - List> partitionedAddresses = Util.partition(filter, 900); - List cursors = new LinkedList<>(); + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + List> splitRecipientIds = Util.partition(filter, 900); + List cursors = new LinkedList<>(); - for (List
addresses : partitionedAddresses) { - String selection = TABLE_NAME + "." + ADDRESS + " = ?"; - String[] selectionArgs = new String[addresses.size()]; + for (List recipientIds : splitRecipientIds) { + String selection = TABLE_NAME + "." + RECIPIENT_ID + " = ?"; + String[] selectionArgs = new String[recipientIds.size()]; - for (int i=0;i settings; - Optional groupRecord; - - if (distributionType != DistributionTypes.ARCHIVE && distributionType != DistributionTypes.INBOX_ZERO) { - settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(cursor); - groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(cursor); - } else { - settings = Optional.absent(); - groupRecord = Optional.absent(); - } - - Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); + Recipient recipient = Recipient.live(recipientId).get(); String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); diff --git a/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java index effa90ea78..b6aa5f8d04 100644 --- a/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java +++ b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java @@ -1,5 +1,10 @@ package org.thoughtcrime.securesms.database.documents; +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.logging.Log; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -13,20 +18,26 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; import java.io.IOException; +import java.util.Objects; public class IdentityKeyMismatch { private static final String TAG = IdentityKeyMismatch.class.getSimpleName(); + /** DEPRECATED */ @JsonProperty(value = "a") private String address; + @JsonProperty(value = "r") + private String recipientId; + @JsonProperty(value = "k") @JsonSerialize(using = IdentityKeySerializer.class) @JsonDeserialize(using = IdentityKeyDeserializer.class) @@ -34,14 +45,19 @@ public class IdentityKeyMismatch { public IdentityKeyMismatch() {} - public IdentityKeyMismatch(Address address, IdentityKey identityKey) { - this.address = address.serialize(); + public IdentityKeyMismatch(RecipientId recipientId, IdentityKey identityKey) { + this.recipientId = recipientId.serialize(); + this.address = ""; this.identityKey = identityKey; } @JsonIgnore - public Address getAddress() { - return Address.fromSerialized(address); + public RecipientId getRecipientId(@NonNull Context context) { + if (!TextUtils.isEmpty(recipientId)) { + return RecipientId.from(recipientId); + } else { + return Recipient.external(context, address).getId(); + } } public IdentityKey getIdentityKey() { @@ -49,18 +65,18 @@ public class IdentityKeyMismatch { } @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof IdentityKeyMismatch)) { - return false; - } - - IdentityKeyMismatch that = (IdentityKeyMismatch)other; - return that.address.equals(this.address) && that.identityKey.equals(this.identityKey); + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IdentityKeyMismatch that = (IdentityKeyMismatch) o; + return Objects.equals(address, that.address) && + Objects.equals(recipientId, that.recipientId) && + Objects.equals(identityKey, that.identityKey); } @Override public int hashCode() { - return address.hashCode() ^ identityKey.hashCode(); + return Objects.hash(address, recipientId, identityKey); } private static class IdentityKeySerializer extends JsonSerializer { diff --git a/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java index 63861c9efc..4ff7baf80f 100644 --- a/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java +++ b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java @@ -1,36 +1,56 @@ package org.thoughtcrime.securesms.database.documents; +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; + +import java.util.Objects; public class NetworkFailure { + /** DEPRECATED */ @JsonProperty(value = "a") private String address; - public NetworkFailure(Address address) { - this.address = address.serialize(); + @JsonProperty(value = "r") + private String recipientId; + + public NetworkFailure(@NonNull RecipientId recipientId) { + this.recipientId = recipientId.serialize(); + this.address = ""; } public NetworkFailure() {} @JsonIgnore - public Address getAddress() { - return Address.fromSerialized(address); + public RecipientId getRecipientId(@NonNull Context context) { + if (!TextUtils.isEmpty(recipientId)) { + return RecipientId.from(recipientId); + } else { + return Recipient.external(context, address).getId(); + } } - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof NetworkFailure)) return false; - NetworkFailure that = (NetworkFailure)other; - return this.address.equals(that.address); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NetworkFailure that = (NetworkFailure) o; + return Objects.equals(address, that.address) && + Objects.equals(recipientId, that.recipientId); } @Override public int hashCode() { - return address.hashCode(); + return Objects.hash(address, recipientId); } } diff --git a/src/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java b/src/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java new file mode 100644 index 0000000000..a1e4e66101 --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java @@ -0,0 +1,282 @@ +package org.thoughtcrime.securesms.database.helpers; + +import android.content.ContentValues; +import android.database.Cursor; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.sqlcipher.database.SQLiteDatabase; + +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; +import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.util.Util; + +import java.util.HashSet; +import java.util.Set; + +public class RecipientIdMigrationHelper { + + private static final String TAG = Log.tag(RecipientIdMigrationHelper.class); + + public static void execute(SQLiteDatabase db) { + Log.i(TAG, "Starting the recipient ID migration."); + + long insertStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting inserts for missing recipients."); + db.execSQL(buildInsertMissingRecipientStatement("identities", "address")); + db.execSQL(buildInsertMissingRecipientStatement("sessions", "address")); + db.execSQL(buildInsertMissingRecipientStatement("thread", "recipient_ids")); + db.execSQL(buildInsertMissingRecipientStatement("sms", "address")); + db.execSQL(buildInsertMissingRecipientStatement("mms", "address")); + db.execSQL(buildInsertMissingRecipientStatement("mms", "quote_author")); + db.execSQL(buildInsertMissingRecipientStatement("group_receipts", "address")); + db.execSQL(buildInsertMissingRecipientStatement("groups", "group_id")); + Log.i(TAG, "Finished inserts for missing recipients in " + (System.currentTimeMillis() - insertStart) + " ms."); + + long updateMissingStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting updates for invalid or missing addresses."); + db.execSQL(buildMissingAddressUpdateStatement("sms", "address")); + db.execSQL(buildMissingAddressUpdateStatement("mms", "address")); + db.execSQL(buildMissingAddressUpdateStatement("mms", "quote_author")); + Log.i(TAG, "Finished updates for invalid or missing addresses in " + (System.currentTimeMillis() - updateMissingStart) + " ms."); + + db.execSQL("ALTER TABLE groups ADD COLUMN recipient_id INTEGER DEFAULT 0"); + + long updateStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting recipient ID updates."); + db.execSQL(buildUpdateAddressToRecipientIdStatement("identities", "address")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("sessions", "address")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("thread", "recipient_ids")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("sms", "address")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "address")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "quote_author")); + db.execSQL(buildUpdateAddressToRecipientIdStatement("group_receipts", "address")); + db.execSQL("UPDATE groups SET recipient_id = (SELECT _id FROM recipient_preferences WHERE recipient_preferences.recipient_ids = groups.group_id)"); + Log.i(TAG, "Finished recipient ID updates in " + (System.currentTimeMillis() - updateStart) + " ms."); + + // NOTE: Because there's an open cursor on the same table, inserts and updates aren't visible + // until afterwards, which is why this group stuff is split into multiple loops + + long findGroupStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting to find missing group recipients."); + Set missingGroupMembers = new HashSet<>(); + + try (Cursor cursor = db.rawQuery("SELECT members FROM groups", null)) { + while (cursor != null && cursor.moveToNext()) { + String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members")); + String[] members = DelimiterUtil.split(serializedMembers, ','); + + for (String rawMember : members) { + String member = DelimiterUtil.unescape(rawMember, ','); + + if (!TextUtils.isEmpty(member) && !recipientExists(db, member)) { + missingGroupMembers.add(member); + } + } + } + } + Log.i(TAG, "Finished finding " + missingGroupMembers.size() + " missing group recipients in " + (System.currentTimeMillis() - findGroupStart) + " ms."); + + long insertGroupStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting the insert of missing group recipients."); + for (String member : missingGroupMembers) { + ContentValues values = new ContentValues(); + values.put("recipient_ids", member); + db.insert("recipient_preferences", null, values); + } + Log.i(TAG, "Finished inserting missing group recipients in " + (System.currentTimeMillis() - insertGroupStart) + " ms."); + + long updateGroupStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting group recipient ID updates."); + try (Cursor cursor = db.rawQuery("SELECT _id, members FROM groups", null)) { + while (cursor != null && cursor.moveToNext()) { + long groupId = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); + String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members")); + String[] members = DelimiterUtil.split(serializedMembers, ','); + long[] memberIds = new long[members.length]; + + for (int i = 0; i < members.length; i++) { + String member = DelimiterUtil.unescape(members[i], ','); + memberIds[i] = requireRecipientId(db, member); + } + + String serializedMemberIds = Util.join(memberIds, ","); + + db.execSQL("UPDATE groups SET members = ? WHERE _id = ?", new String[]{ serializedMemberIds, String.valueOf(groupId) }); + } + } + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_recipient_id_index ON groups (recipient_id)"); + Log.i(TAG, "Finished group recipient ID updates in " + (System.currentTimeMillis() - updateGroupStart) + " ms."); + + + long tableCopyStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting to copy the recipient table."); + db.execSQL("CREATE TABLE recipient (_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "uuid TEXT UNIQUE DEFAULT NULL, " + + "phone TEXT UNIQUE DEFAULT NULL, " + + "email TEXT UNIQUE DEFAULT NULL, " + + "group_id TEXT UNIQUE DEFAULT NULL, " + + "blocked INTEGER DEFAULT 0, " + + "message_ringtone TEXT DEFAULT NULL, " + + "message_vibrate INTEGER DEFAULT 0, " + + "call_ringtone TEXT DEFAULT NULL, " + + "call_vibrate INTEGER DEFAULT 0, " + + "notification_channel TEXT DEFAULT NULL, " + + "mute_until INTEGER DEFAULT 0, " + + "color TEXT DEFAULT NULL, " + + "seen_invite_reminder INTEGER DEFAULT 0, " + + "default_subscription_id INTEGER DEFAULT -1, " + + "message_expiration_time INTEGER DEFAULT 0, " + + "registered INTEGER DEFAULT 0, " + + "system_display_name TEXT DEFAULT NULL, " + + "system_photo_uri TEXT DEFAULT NULL, " + + "system_phone_label TEXT DEFAULT NULL, " + + "system_contact_uri TEXT DEFAULT NULL, " + + "profile_key TEXT DEFAULT NULL, " + + "signal_profile_name TEXT DEFAULT NULL, " + + "signal_profile_avatar TEXT DEFAULT NULL, " + + "profile_sharing INTEGER DEFAULT 0, " + + "unidentified_access_mode INTEGER DEFAULT 0, " + + "force_sms_selection INTEGER DEFAULT 0)"); + + try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); + boolean isGroup = GroupUtil.isEncodedGroup(address); + boolean isEmail = !isGroup && NumberUtil.isValidEmail(address); + boolean isPhone = !isGroup && !isEmail; + + ContentValues values = new ContentValues(); + + values.put("_id", cursor.getLong(cursor.getColumnIndexOrThrow("_id"))); + values.put("uuid", (String) null); + values.put("phone", isPhone ? address : null); + values.put("email", isEmail ? address : null); + values.put("group_id", isGroup ? address : null); + values.put("blocked", cursor.getInt(cursor.getColumnIndexOrThrow("block"))); + values.put("message_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("notification"))); + values.put("message_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("vibrate"))); + values.put("call_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("call_ringtone"))); + values.put("call_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("call_vibrate"))); + values.put("notification_channel", cursor.getString(cursor.getColumnIndexOrThrow("notification_channel"))); + values.put("mute_until", cursor.getLong(cursor.getColumnIndexOrThrow("mute_until"))); + values.put("color", cursor.getString(cursor.getColumnIndexOrThrow("color"))); + values.put("seen_invite_reminder", cursor.getInt(cursor.getColumnIndexOrThrow("seen_invite_reminder"))); + values.put("default_subscription_id", cursor.getInt(cursor.getColumnIndexOrThrow("default_subscription_id"))); + values.put("message_expiration_time", cursor.getInt(cursor.getColumnIndexOrThrow("expire_messages"))); + values.put("registered", cursor.getInt(cursor.getColumnIndexOrThrow("registered"))); + values.put("system_display_name", cursor.getString(cursor.getColumnIndexOrThrow("system_display_name"))); + values.put("system_photo_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_photo"))); + values.put("system_phone_label", cursor.getString(cursor.getColumnIndexOrThrow("system_phone_label"))); + values.put("system_contact_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_uri"))); + values.put("profile_key", cursor.getString(cursor.getColumnIndexOrThrow("profile_key"))); + values.put("signal_profile_name", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name"))); + values.put("signal_profile_avatar", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_avatar"))); + values.put("profile_sharing", cursor.getInt(cursor.getColumnIndexOrThrow("profile_sharing_approval"))); + values.put("unidentified_access_mode", cursor.getInt(cursor.getColumnIndexOrThrow("unidentified_access_mode"))); + values.put("force_sms_selection", cursor.getInt(cursor.getColumnIndexOrThrow("force_sms_selection"))); + + db.insert("recipient", null, values); + } + } + + db.execSQL("DROP TABLE recipient_preferences"); + Log.i(TAG, "Finished copying the recipient table in " + (System.currentTimeMillis() - tableCopyStart) + " ms."); + + long sanityCheckStart = System.currentTimeMillis(); + + Log.i(TAG, "Starting DB integrity sanity checks."); + assertEmptyQuery(db, "identities", buildSanityCheckQuery("identities", "address")); + assertEmptyQuery(db, "sessions", buildSanityCheckQuery("sessions", "address")); + assertEmptyQuery(db, "groups", buildSanityCheckQuery("groups", "recipient_id")); + assertEmptyQuery(db, "thread", buildSanityCheckQuery("thread", "recipient_ids")); + assertEmptyQuery(db, "sms", buildSanityCheckQuery("sms", "address")); + assertEmptyQuery(db, "mms -- address", buildSanityCheckQuery("mms", "address")); + assertEmptyQuery(db, "mms -- quote_author", buildSanityCheckQuery("mms", "quote_author")); + assertEmptyQuery(db, "group_receipts", buildSanityCheckQuery("group_receipts", "address")); + Log.i(TAG, "Finished DB integrity sanity checks in " + (System.currentTimeMillis() - sanityCheckStart) + " ms."); + + Log.i(TAG, "Finished recipient ID migration in " + (System.currentTimeMillis() - insertStart) + " ms."); + } + + private static String buildUpdateAddressToRecipientIdStatement(@NonNull String table, @NonNull String addressColumn) { + return "UPDATE " + table + " SET " + addressColumn + "=(SELECT _id " + + "FROM recipient_preferences " + + "WHERE recipient_preferences.recipient_ids = " + table + "." + addressColumn + ")"; + } + + private static String buildInsertMissingRecipientStatement(@NonNull String table, @NonNull String addressColumn) { + return "INSERT INTO recipient_preferences(recipient_ids) SELECT DISTINCT " + addressColumn + " " + + "FROM " + table + " " + + "WHERE " + addressColumn + " != '' AND " + + addressColumn + " != 'insert-address-column' AND " + + addressColumn + " NOT NULL AND " + + addressColumn + " NOT IN (SELECT recipient_ids FROM recipient_preferences)"; + } + + private static String buildMissingAddressUpdateStatement(@NonNull String table, @NonNull String addressColumn) { + return "UPDATE " + table + " SET " + addressColumn + " = -1 " + + "WHERE " + addressColumn + " = '' OR " + + addressColumn + " IS NULL OR " + + addressColumn + " = 'insert-address-token'"; + } + + private static boolean recipientExists(@NonNull SQLiteDatabase db, @NonNull String address) { + return getRecipientId(db, address) != null; + } + + private static @Nullable Long getRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) { + try (Cursor cursor = db.rawQuery("SELECT _id FROM recipient_preferences WHERE recipient_ids = ?", new String[]{ address })) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex("_id")); + } else { + return null; + } + } + } + + private static long requireRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) { + Long id = getRecipientId(db, address); + + if (id != null) { + return id; + } else { + throw new MissingRecipientError(address); + } + } + + private static String buildSanityCheckQuery(@NonNull String table, @NonNull String idColumn) { + return "SELECT " + idColumn + " FROM " + table + " WHERE " + idColumn + " != -1 AND " + idColumn + " NOT IN (SELECT _id FROM recipient)"; + } + + private static void assertEmptyQuery(@NonNull SQLiteDatabase db, @NonNull String tag, @NonNull String query) { + try (Cursor cursor = db.rawQuery(query, null)) { + if (cursor != null && cursor.moveToFirst()) { + throw new FailedSanityCheckError(tag); + } + } + } + + private static final class MissingRecipientError extends AssertionError { + MissingRecipientError(@NonNull String address) { + super("Could not find recipient with address " + address); + } + } + + private static final class FailedSanityCheckError extends AssertionError { + FailedSanityCheckError(@NonNull String tableName) { + super("Sanity check failed for tag '" + tableName + "'"); + } + } +} diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index d9a2307f03..3eec65a5f7 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -7,6 +7,8 @@ import android.database.Cursor; import android.net.Uri; import android.os.SystemClock; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import android.text.TextUtils; import net.sqlcipher.database.SQLiteDatabase; @@ -36,8 +38,13 @@ import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import java.io.File; @@ -68,8 +75,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int STICKERS = 21; private static final int REVEALABLE_MESSAGES = 22; private static final int VIEW_ONCE_ONLY = 23; + private static final int RECIPIENT_IDS = 24; - private static final int DATABASE_VERSION = 23; + private static final int DATABASE_VERSION = 25; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -287,18 +295,18 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { try (Cursor cursor = db.rawQuery("SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0", null)) { while (cursor != null && cursor.moveToNext()) { - String addressString = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - Address address = Address.fromExternal(context, addressString); + String rawAddress = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); + String address = PhoneNumberFormatter.get(context).format(rawAddress); String systemName = cursor.getString(cursor.getColumnIndexOrThrow("system_display_name")); String profileName = cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name")); String messageSound = cursor.getString(cursor.getColumnIndexOrThrow("notification")); Uri messageSoundUri = messageSound != null ? Uri.parse(messageSound) : null; int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate")); - String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, address); + String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, Address.fromSerialized(address)); boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1; - if (address.isGroup()) { - try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address.toGroupString() })) { + if (GroupUtil.isEncodedGroup(address)) { + try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address })) { if (groupCursor != null && groupCursor.moveToFirst()) { String title = groupCursor.getString(groupCursor.getColumnIndexOrThrow("title")); @@ -309,11 +317,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } - String channelId = NotificationChannels.createChannelFor(context, address, displayName, messageSoundUri, vibrateEnabled); + String channelId = NotificationChannels.createChannelFor(context, Address.fromSerialized(address), displayName, messageSoundUri, vibrateEnabled); ContentValues values = new ContentValues(1); values.put("notification_channel", channelId); - db.update("recipient_preferences", values, "recipient_ids = ?", new String[] { addressString }); + db.update("recipient_preferences", values, "recipient_ids = ?", new String[] { rawAddress }); } } } @@ -478,6 +486,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("UPDATE mms SET reveal_start_time = 0"); } + if (oldVersion < RECIPIENT_IDS) { + RecipientIdMigrationHelper.execute(db); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java index 6d37b13909..072583f634 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java @@ -79,7 +79,7 @@ class SessionStoreMigrationHelper { ContentValues contentValues = new ContentValues(); - contentValues.put(SessionDatabase.ADDRESS, address.serialize()); + contentValues.put(SessionDatabase.RECIPIENT_ID, address.serialize()); contentValues.put(SessionDatabase.DEVICE, deviceId); contentValues.put(SessionDatabase.RECORD, sessionRecord.serialize()); diff --git a/src/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java b/src/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java index a92b060382..100bb80042 100644 --- a/src/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java +++ b/src/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java @@ -76,7 +76,7 @@ public class IdentityRecordList { for (IdentityRecord identityRecord : identityRecords) { if (isUntrusted(identityRecord)) { - untrusted.add(Recipient.from(context, identityRecord.getAddress(), false)); + untrusted.add(Recipient.resolved(identityRecord.getRecipientId())); } } @@ -100,7 +100,7 @@ public class IdentityRecordList { for (IdentityRecord identityRecord : identityRecords) { if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) { - unverified.add(Recipient.from(context, identityRecord.getAddress(), false)); + unverified.add(Recipient.resolved(identityRecord.getRecipientId())); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java index a514e015b3..0707a6d5f1 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java @@ -14,8 +14,7 @@ public class BlockedContactsLoader extends AbstractCursorLoader { @Override public Cursor getCursor() { - return DatabaseFactory.getRecipientDatabase(getContext()) - .getBlocked(); + return DatabaseFactory.getRecipientDatabase(getContext()).getBlocked(); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 3ab7b7a95f..50e17c8059 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -31,13 +32,13 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader 0) { MatrixCursor switchToArchiveCursor = new MatrixCursor(new String[] { ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, - ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.UNREAD_COUNT, + ThreadDatabase.RECIPIENT_ID, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.UNREAD_COUNT, ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, ThreadDatabase.SNIPPET_CONTENT_TYPE, ThreadDatabase.SNIPPET_EXTRAS, ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT, @@ -70,13 +73,13 @@ public class ConversationListLoader extends AbstractCursorLoader { } private Cursor getFilteredConversationList(String filter) { - List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter); - List
addresses = new LinkedList<>(); + List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter); + List recipientIds = new LinkedList<>(); for (String number : numbers) { - addresses.add(Address.fromExternal(context, number)); + recipientIds.add(Recipient.external(context, number).getId()); } - return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(addresses); + return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(recipientIds); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java index 0e8fc9a0b5..7f28923ceb 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java @@ -8,29 +8,30 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AbstractCursorLoader; public class ThreadMediaLoader extends AbstractCursorLoader { - private final Address address; - private final boolean gallery; + private final RecipientId recipientId; + private final boolean gallery; - public ThreadMediaLoader(@NonNull Context context, @NonNull Address address, boolean gallery) { + public ThreadMediaLoader(@NonNull Context context, @NonNull RecipientId recipientId, boolean gallery) { super(context); - this.address = address; - this.gallery = gallery; + this.recipientId = recipientId; + this.gallery = gallery; } @Override public Cursor getCursor() { - long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true)); + long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.resolved(recipientId)); if (gallery) return DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId); else return DatabaseFactory.getMediaDatabase(getContext()).getDocumentMediaForThread(threadId); } - public Address getAddress() { - return address; + public RecipientId getRecipientId() { + return recipientId; } } diff --git a/src/org/thoughtcrime/securesms/database/model/Quote.java b/src/org/thoughtcrime/securesms/database/model/Quote.java index 638ca9d71a..85c6b84c1f 100644 --- a/src/org/thoughtcrime/securesms/database/model/Quote.java +++ b/src/org/thoughtcrime/securesms/database/model/Quote.java @@ -6,16 +6,18 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.SlideDeck; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; public class Quote { - private final long id; - private final Address author; - private final String text; - private final boolean missing; - private final SlideDeck attachment; + private final long id; + private final RecipientId author; + private final String text; + private final boolean missing; + private final SlideDeck attachment; - public Quote(long id, @NonNull Address author, @Nullable String text, boolean missing, @NonNull SlideDeck attachment) { + public Quote(long id, @NonNull RecipientId author, @Nullable String text, boolean missing, @NonNull SlideDeck attachment) { this.id = id; this.author = author; this.text = text; @@ -27,7 +29,7 @@ public class Quote { return id; } - public @NonNull Address getAuthor() { + public @NonNull RecipientId getAuthor() { return author; } diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index f735b20e19..9da3b109a4 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -29,7 +29,6 @@ import android.text.style.StyleSpan; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase.Extra; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.ExpirationUtil; @@ -111,7 +110,7 @@ public class ThreadRecord extends DisplayRecord { String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time)); } else if (SmsDatabase.Types.isIdentityUpdate(type)) { - if (getRecipient().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); + if (getRecipient().isGroup()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString())); } else if (SmsDatabase.Types.isIdentityVerified(type)) { return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified)); diff --git a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index e6d5209cff..e7123ca8fb 100644 --- a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.dependencies; import android.app.Application; -import android.content.Context; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.IncomingMessageProcessor; import org.thoughtcrime.securesms.gcm.MessageRetriever; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; +import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceAccountManager; @@ -32,6 +32,7 @@ public class ApplicationDependencies { private static SignalServiceMessageReceiver messageReceiver; private static IncomingMessageProcessor incomingMessageProcessor; private static MessageRetriever messageRetriever; + private static LiveRecipientCache recipientCache; public static synchronized void init(@NonNull Application application, @NonNull Provider provider) { if (ApplicationDependencies.application != null || ApplicationDependencies.provider != null) { @@ -105,6 +106,16 @@ public class ApplicationDependencies { return messageRetriever; } + public static synchronized @NonNull LiveRecipientCache getRecipientCache() { + assertInitialization(); + + if (recipientCache == null) { + recipientCache = provider.provideRecipientCache(); + } + + return recipientCache; + } + private static void assertInitialization() { if (application == null || provider == null) { throw new UninitializedException(); @@ -118,6 +129,8 @@ public class ApplicationDependencies { @NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess(); @NonNull IncomingMessageProcessor provideIncomingMessageProcessor(); @NonNull MessageRetriever provideMessageRetriever(); + @NonNull + LiveRecipientCache provideRecipientCache(); } private static class UninitializedException extends IllegalStateException { diff --git a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index f6629ca7cc..20b8a17e81 100644 --- a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.gcm.MessageRetriever; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; +import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; @@ -85,6 +86,12 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr return new MessageRetriever(); } + @Override + public @NonNull + LiveRecipientCache provideRecipientCache() { + return new LiveRecipientCache(context); + } + private static class DynamicCredentialsProvider implements CredentialsProvider { private final Context context; diff --git a/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java b/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java index a376b888e9..a2be24ec19 100644 --- a/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java +++ b/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java @@ -118,6 +118,6 @@ public class WebRtcViewModel { } public @NonNull String toString() { - return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]"; + return "[State: " + state + ", recipient: " + recipient.requireAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]"; } } diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java index 4d6eb04def..421fc6e8be 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.util.Collection; @@ -22,7 +23,7 @@ public final class GroupManager { @Nullable String name, boolean mms) { - Set
addresses = getMemberAddresses(members); + Set addresses = getMemberIds(members); return V1GroupManager.createGroup(context, addresses, avatar, name, mms); } @@ -34,15 +35,15 @@ public final class GroupManager { @Nullable String name) throws InvalidNumberException { - Set
addresses = getMemberAddresses(members); + Set addresses = getMemberIds(members); return V1GroupManager.updateGroup(context, groupId, addresses, avatar, name); } - private static Set
getMemberAddresses(Collection recipients) { - final Set
results = new HashSet<>(); + private static Set getMemberIds(Collection recipients) { + final Set results = new HashSet<>(); for (Recipient recipient : recipients) { - results.add(recipient.getAddress()); + results.add(recipient.getId()); } return results; diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 77f53a65ef..a98e86661c 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -8,7 +8,6 @@ import androidx.annotation.Nullable; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; @@ -21,6 +20,7 @@ import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.Base64; @@ -86,11 +86,11 @@ public class GroupMessageProcessor { builder.setType(GroupContext.Type.UPDATE); SignalServiceAttachment avatar = group.getAvatar().orNull(); - List
members = group.getMembers().isPresent() ? new LinkedList
() : null; + List members = new LinkedList<>(); if (group.getMembers().isPresent()) { for (String member : group.getMembers().get()) { - members.add(Address.fromExternal(context, member)); + members.add(Recipient.external(context, member).getId()); } } @@ -110,31 +110,31 @@ public class GroupMessageProcessor { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); String id = GroupUtil.getEncodedId(group.getGroupId(), false); - Set
recordMembers = new HashSet<>(groupRecord.getMembers()); - Set
messageMembers = new HashSet<>(); + Set recordMembers = new HashSet<>(groupRecord.getMembers()); + Set messageMembers = new HashSet<>(); for (String messageMember : group.getMembers().get()) { - messageMembers.add(Address.fromExternal(context, messageMember)); + messageMembers.add(Recipient.external(context, messageMember).getId()); } - Set
addedMembers = new HashSet<>(messageMembers); + Set addedMembers = new HashSet<>(messageMembers); addedMembers.removeAll(recordMembers); - Set
missingMembers = new HashSet<>(recordMembers); + Set missingMembers = new HashSet<>(recordMembers); missingMembers.removeAll(messageMembers); GroupContext.Builder builder = createGroupContext(group); builder.setType(GroupContext.Type.UPDATE); if (addedMembers.size() > 0) { - Set
unionMembers = new HashSet<>(recordMembers); + Set unionMembers = new HashSet<>(recordMembers); unionMembers.addAll(messageMembers); database.updateMembers(id, new LinkedList<>(unionMembers)); builder.clearMembers(); - for (Address addedMember : addedMembers) { - builder.addMembers(addedMember.serialize()); + for (RecipientId addedMember : addedMembers) { + builder.addMembers(Recipient.resolved(addedMember).requireAddress().serialize()); } } else { builder.clearMembers(); @@ -163,10 +163,12 @@ public class GroupMessageProcessor { @NonNull SignalServiceGroup group, @NonNull GroupRecord record) { - if (record.getMembers().contains(Address.fromExternal(context, content.getSender()))) { + Recipient sender = Recipient.external(context, content.getSender()); + + if (record.getMembers().contains(sender.getId())) { ApplicationContext.getInstance(context) .getJobManager() - .add(new PushGroupUpdateJob(content.getSender(), group.getGroupId())); + .add(new PushGroupUpdateJob(sender.getId(), group.getGroupId())); } return null; @@ -178,15 +180,15 @@ public class GroupMessageProcessor { @NonNull GroupRecord record, boolean outgoing) { - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - String id = GroupUtil.getEncodedId(group.getGroupId(), false); - List
members = record.getMembers(); + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + String id = GroupUtil.getEncodedId(group.getGroupId(), false); + List members = record.getMembers(); GroupContext.Builder builder = createGroupContext(group); builder.setType(GroupContext.Type.QUIT); - if (members.contains(Address.fromExternal(context, content.getSender()))) { - database.remove(id, Address.fromExternal(context, content.getSender())); + if (members.contains(Recipient.external(context, content.getSender()).getId())) { + database.remove(id, Recipient.external(context, content.getSender()).getId()); if (outgoing) database.setActive(id, false); return storeMessage(context, content, group, builder.build(), outgoing); @@ -210,8 +212,8 @@ public class GroupMessageProcessor { try { if (outgoing) { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false)); - Recipient recipient = Recipient.from(context, addres, false); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(group.getGroupId(), false)); + Recipient recipient = Recipient.resolved(recipientId); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, false, null, Collections.emptyList(), Collections.emptyList()); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); @@ -222,7 +224,7 @@ public class GroupMessageProcessor { } else { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt()); + IncomingTextMessage incoming = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt()); IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); diff --git a/src/org/thoughtcrime/securesms/groups/V1GroupManager.java b/src/org/thoughtcrime/securesms/groups/V1GroupManager.java index b9fb0beef9..b8a42643ea 100644 --- a/src/org/thoughtcrime/securesms/groups/V1GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/V1GroupManager.java @@ -10,7 +10,6 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -19,6 +18,7 @@ import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; @@ -28,50 +28,49 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.IOException; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; final class V1GroupManager { - static @NonNull GroupActionResult createGroup(@NonNull Context context, - @NonNull Set
memberAddresses, - @Nullable Bitmap avatar, - @Nullable String name, - boolean mms) + static @NonNull GroupActionResult createGroup(@NonNull Context context, + @NonNull Set memberIds, + @Nullable Bitmap avatar, + @Nullable String name, + boolean mms) { - final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); - final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - final String groupId = GroupUtil.getEncodedId(groupDatabase.allocateGroupId(), mms); - final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false); + final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); + final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + final String groupId = GroupUtil.getEncodedId(groupDatabase.allocateGroupId(), mms); + final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + final Recipient groupRecipient = Recipient.resolved(groupRecipientId); - memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); - groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null); + memberIds.add(Recipient.external(context, TextSecurePreferences.getLocalNumber(context)).getId()); + groupDatabase.create(groupId, name, new LinkedList<>(memberIds), null, null); if (!mms) { groupDatabase.updateAvatar(groupId, avatarBytes); - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true); - return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true); + return sendGroupUpdate(context, groupId, memberIds, name, avatarBytes); } else { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); return new GroupActionResult(groupRecipient, threadId); } } - static GroupActionResult updateGroup(@NonNull Context context, - @NonNull String groupId, - @NonNull Set
memberAddresses, - @Nullable Bitmap avatar, - @Nullable String name) + static GroupActionResult updateGroup(@NonNull Context context, + @NonNull String groupId, + @NonNull Set memberAddresses, + @Nullable Bitmap avatar, + @Nullable String name) throws InvalidNumberException { final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); - memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); + memberAddresses.add(Recipient.external(context, TextSecurePreferences.getLocalNumber(context)).getId()); groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses)); groupDatabase.updateTitle(groupId, name); groupDatabase.updateAvatar(groupId, avatarBytes); @@ -79,27 +78,28 @@ final class V1GroupManager { if (!GroupUtil.isMmsGroup(groupId)) { return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes); } else { - Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient groupRecipient = Recipient.resolved(groupRecipientId); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); return new GroupActionResult(groupRecipient, threadId); } } - private static GroupActionResult sendGroupUpdate(@NonNull Context context, - @NonNull String groupId, - @NonNull Set
members, - @Nullable String groupName, - @Nullable byte[] avatar) + private static GroupActionResult sendGroupUpdate(@NonNull Context context, + @NonNull String groupId, + @NonNull Set members, + @Nullable String groupName, + @Nullable byte[] avatar) { try { - Attachment avatarAttachment = null; - Address groupAddress = Address.fromSerialized(groupId); - Recipient groupRecipient = Recipient.from(context, groupAddress, false); + Attachment avatarAttachment = null; + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient groupRecipient = Recipient.resolved(groupRecipientId); List numbers = new LinkedList<>(); - for (Address member : members) { - numbers.add(member.serialize()); + for (RecipientId member : members) { + numbers.add(Recipient.resolved(member).requireAddress().serialize()); } GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() diff --git a/src/org/thoughtcrime/securesms/jobmanager/JobMigration.java b/src/org/thoughtcrime/securesms/jobmanager/JobMigration.java index 5c86926f0c..1f45a5ef3d 100644 --- a/src/org/thoughtcrime/securesms/jobmanager/JobMigration.java +++ b/src/org/thoughtcrime/securesms/jobmanager/JobMigration.java @@ -27,23 +27,23 @@ public abstract class JobMigration { return endVersion; } - protected static class JobData { + public static class JobData { private final String factoryKey; private final String queueKey; private final Data data; - JobData(@NonNull String factoryKey, @Nullable String queueKey, @NonNull Data data) { + public JobData(@NonNull String factoryKey, @Nullable String queueKey, @NonNull Data data) { this.factoryKey = factoryKey; this.queueKey = queueKey; this.data = data; } - protected @NonNull JobData withQueueKey(@Nullable String newQueueKey) { + public @NonNull JobData withQueueKey(@Nullable String newQueueKey) { return new JobData(factoryKey, newQueueKey, data); } - protected @NonNull JobData withData(@NonNull Data newData) { + public @NonNull JobData withData(@NonNull Data newData) { return new JobData(factoryKey, queueKey, newData); } diff --git a/src/org/thoughtcrime/securesms/jobmanager/migrations/RecipientIdJobMigration.java b/src/org/thoughtcrime/securesms/jobmanager/migrations/RecipientIdJobMigration.java new file mode 100644 index 0000000000..a379e06cec --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobmanager/migrations/RecipientIdJobMigration.java @@ -0,0 +1,226 @@ +package org.thoughtcrime.securesms.jobmanager.migrations; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.JobMigration; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.JsonUtils; + +import java.io.IOException; +import java.io.Serializable; + +public class RecipientIdJobMigration extends JobMigration { + + private final Application application; + + public RecipientIdJobMigration(@NonNull Application application) { + super(2); + this.application = application; + } + + @Override + protected @NonNull JobData migrate(@NonNull JobData jobData) { + switch(jobData.getFactoryKey()) { + case "MultiDeviceContactUpdateJob": return migrateMultiDeviceContactUpdateJob(jobData); + case "MultiDeviceRevealUpdateJob": return migrateMultiDeviceViewOnceOpenJob(jobData); + case "RequestGroupInfoJob": return migrateRequestGroupInfoJob(jobData); + case "SendDeliveryReceiptJob": return migrateSendDeliveryReceiptJob(jobData); + case "MultiDeviceVerifiedUpdateJob": return migrateMultiDeviceVerifiedUpdateJob(jobData); + case "RetrieveProfileJob": return migrateRetrieveProfileJob(jobData); + case "PushGroupSendJob": return migratePushGroupSendJob(jobData); + case "PushGroupUpdateJob": return migratePushGroupUpdateJob(jobData); + case "DirectoryRefreshJob": return migrateDirectoryRefreshJob(jobData); + case "RetrieveProfileAvatarJob": return migrateRetrieveProfileAvatarJob(jobData); + case "MultiDeviceReadUpdateJob": return migrateMultiDeviceReadUpdateJob(jobData); + case "PushTextSendJob": return migratePushTextSendJob(jobData); + case "PushMediaSendJob": return migratePushMediaSendJob(jobData); + case "SmsSendJob": return migrateSmsSendJob(jobData); + default: return jobData; + } + } + + private @NonNull JobData migrateMultiDeviceContactUpdateJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("address"); + Data updatedData = new Data.Builder().putString("recipient", Recipient.external(application, address).getId().serialize()) + .putBoolean("force_sync", jobData.getData().getBoolean("force_sync")) + .build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateMultiDeviceViewOnceOpenJob(@NonNull JobData jobData) { + try { + String rawOld = jobData.getData().getString("message_id"); + OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld, OldSerializableSyncMessageId.class); + Recipient recipient = Recipient.external(application, old.sender); + NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp); + String rawUpdated = JsonUtils.toJson(updated); + Data updatedData = new Data.Builder().putString("message_id", rawUpdated).build(); + + return jobData.withData(updatedData); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("source"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("source", recipient.getId().serialize()).build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("address"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize()).build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateMultiDeviceVerifiedUpdateJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("destination"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("destination", recipient.getId().serialize()) + .putString("identity_key", jobData.getData().getString("identity_key")) + .putInt("verified_status", jobData.getData().getInt("verified_status")) + .putLong("timestamp", jobData.getData().getLong("timestamp")) + .build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateRetrieveProfileJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("address"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize()).build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migratePushGroupSendJob(@NonNull JobData jobData) { + // noinspection ConstantConditions + Recipient queueRecipient = Recipient.external(application, jobData.getQueueKey()); + String address = jobData.getData().getString("filter_address"); + RecipientId recipientId = address != null ? Recipient.external(application, address).getId() : null; + Data updatedData = new Data.Builder().putString("filter_recipient", recipientId != null ? recipientId.serialize() : null) + .putLong("message_id", jobData.getData().getLong("message_id")) + .build(); + + return jobData.withQueueKey(queueRecipient.getId().toQueueKey()) + .withData(updatedData); + } + + private @NonNull JobData migratePushGroupUpdateJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("source"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("source", recipient.getId().serialize()) + .putString("group_id", jobData.getData().getString("group_id")) + .build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateDirectoryRefreshJob(@NonNull JobData jobData) { + String address = jobData.getData().getString("address"); + Recipient recipient = address != null ? Recipient.external(application, address) : null; + Data updatedData = new Data.Builder().putString("recipient", recipient != null ? recipient.getId().serialize() : null) + .putBoolean("notify_of_new_users", jobData.getData().getBoolean("notify_of_new_users")) + .build(); + + return jobData.withData(updatedData); + } + + private @NonNull JobData migrateRetrieveProfileAvatarJob(@NonNull JobData jobData) { + //noinspection ConstantConditions + String queueAddress = jobData.getQueueKey().substring("RetrieveProfileAvatarJob".length()); + Recipient queueRecipient = Recipient.external(application, queueAddress); + String address = jobData.getData().getString("address"); + Recipient recipient = Recipient.external(application, address); + Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize()) + .putString("profile_avatar", jobData.getData().getString("profile_avatar")) + .build(); + + return jobData.withQueueKey("RetrieveProfileAvatarJob::" + queueRecipient.getId().toQueueKey()) + .withData(updatedData); + } + + private @NonNull JobData migrateMultiDeviceReadUpdateJob(@NonNull JobData jobData) { + try { + String[] rawOld = jobData.getData().getStringArray("message_ids"); + String[] rawUpdated = new String[rawOld.length]; + + for (int i = 0; i < rawOld.length; i++) { + OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld[i], OldSerializableSyncMessageId.class); + Recipient recipient = Recipient.external(application, old.sender); + NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp); + + rawUpdated[i] = JsonUtils.toJson(updated); + } + + Data updatedData = new Data.Builder().putStringArray("message_ids", rawUpdated).build(); + + return jobData.withData(updatedData); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private @NonNull JobData migratePushTextSendJob(@NonNull JobData jobData) { + //noinspection ConstantConditions + Recipient recipient = Recipient.external(application, jobData.getQueueKey()); + return jobData.withQueueKey(recipient.getId().toQueueKey()); + } + + private @NonNull JobData migratePushMediaSendJob(@NonNull JobData jobData) { + //noinspection ConstantConditions + Recipient recipient = Recipient.external(application, jobData.getQueueKey()); + return jobData.withQueueKey(recipient.getId().toQueueKey()); + } + + private @NonNull JobData migrateSmsSendJob(@NonNull JobData jobData) { + //noinspection ConstantConditions + Recipient recipient = Recipient.external(application, jobData.getQueueKey()); + return jobData.withQueueKey(recipient.getId().toQueueKey()); + } + + @VisibleForTesting + static class OldSerializableSyncMessageId implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty + private final String sender; + @JsonProperty + private final long timestamp; + + OldSerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) { + this.sender = sender; + this.timestamp = timestamp; + } + } + + @VisibleForTesting + static class NewSerializableSyncMessageId implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty + private final String recipientId; + @JsonProperty + private final long timestamp; + + NewSerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) { + this.recipientId = recipientId; + this.timestamp = timestamp; + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java index 590ff376ef..dd51c23f6a 100644 --- a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java +++ b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java @@ -1,16 +1,15 @@ package org.thoughtcrime.securesms.jobs; -import android.app.Application; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; @@ -22,7 +21,7 @@ public class DirectoryRefreshJob extends BaseJob { private static final String TAG = DirectoryRefreshJob.class.getSimpleName(); - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private static final String KEY_NOTIFY_OF_NEW_USERS = "notify_of_new_users"; @Nullable private Recipient recipient; @@ -53,7 +52,7 @@ public class DirectoryRefreshJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_ADDRESS, recipient != null ? recipient.getAddress().serialize() : null) + return new Data.Builder().putString(KEY_RECIPIENT, recipient != null ? recipient.getId().serialize() : null) .putBoolean(KEY_NOTIFY_OF_NEW_USERS, notifyOfNewUsers) .build(); } @@ -85,18 +84,11 @@ public class DirectoryRefreshJob extends BaseJob { public static final class Factory implements Job.Factory { - private final Application application; - - public Factory(@NonNull Application application) { - this.application = application; - } - @Override public @NonNull DirectoryRefreshJob create(@NonNull Parameters parameters, @NonNull Data data) { - String serializedAddress = data.getString(KEY_ADDRESS); - Address address = serializedAddress != null ? Address.fromSerialized(serializedAddress) : null; - Recipient recipient = address != null ? Recipient.from(application, address, true) : null; - boolean notifyOfNewUsers = data.getBoolean(KEY_NOTIFY_OF_NEW_USERS); + String serialized = data.getString(KEY_RECIPIENT); + Recipient recipient = serialized != null ? Recipient.resolved(RecipientId.from(serialized)) : null; + boolean notifyOfNewUsers = data.getBoolean(KEY_NOTIFY_OF_NEW_USERS); return new DirectoryRefreshJob(parameters, recipient, notifyOfNewUsers); } diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 4ff7274e10..3fd008f24f 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -14,12 +14,12 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; +import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,7 +35,7 @@ public final class JobManagerFactories { put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory()); put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory()); - put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory(application)); + put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory()); put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory()); @@ -61,13 +61,13 @@ public final class JobManagerFactories { put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory()); put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory()); put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory()); - put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); - put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory(application)); + put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory()); + put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory()); put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory()); put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory()); put(RotateSignedPreKeyJob.KEY, new RotateSignedPreKeyJob.Factory()); put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory()); - put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory()); + put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application)); put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory()); put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory()); put(SmsSendJob.KEY, new SmsSendJob.Factory()); @@ -105,7 +105,7 @@ public final class JobManagerFactories { new SqlCipherMigrationConstraintObserver()); } - public static List getJobMigrations() { - return Collections.emptyList(); + public static List getJobMigrations(@NonNull Application application) { + return Arrays.asList(new RecipientIdJobMigration(application)); } } diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index af7577f097..6c92ee788c 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -4,6 +4,7 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.annimon.stream.Stream; import com.google.android.mms.pdu_alt.CharacterSets; import com.google.android.mms.pdu_alt.EncodedStringValue; import com.google.android.mms.pdu_alt.PduBody; @@ -28,6 +29,8 @@ import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -39,6 +42,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -155,18 +159,6 @@ public class MmsDownloadJob extends BaseJob { handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE, automatic); - } catch (DuplicateMessageException e) { - Log.w(TAG, e); - database.markAsDecryptDuplicate(messageId, threadId); - } catch (LegacyMessageException e) { - Log.w(TAG, e); - database.markAsLegacyVersion(messageId, threadId); - } catch (NoSessionException e) { - Log.w(TAG, e); - database.markAsNoSession(messageId, threadId); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - database.markAsDecryptFailed(messageId, threadId); } } @@ -188,40 +180,39 @@ public class MmsDownloadJob extends BaseJob { private void storeRetrievedMms(String contentLocation, long messageId, long threadId, RetrieveConf retrieved, - int subscriptionId, @Nullable Address notificationFrom) - throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException, - LegacyMessageException + int subscriptionId, @Nullable RecipientId notificationFrom) + throws MmsException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Optional
group = Optional.absent(); - Set
members = new HashSet<>(); - String body = null; - List attachments = new LinkedList<>(); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Optional group = Optional.absent(); + Set members = new HashSet<>(); + String body = null; + List attachments = new LinkedList<>(); - Address from; + RecipientId from; if (retrieved.getFrom() != null) { - from = Address.fromExternal(context, Util.toIsoString(retrieved.getFrom().getTextString())); + from = Recipient.external(context, Util.toIsoString(retrieved.getFrom().getTextString())).getId(); } else if (notificationFrom != null) { from = notificationFrom; } else { - from = Address.UNKNOWN; + from = RecipientId.UNKNOWN; } if (retrieved.getTo() != null) { for (EncodedStringValue toValue : retrieved.getTo()) { - members.add(Address.fromExternal(context, Util.toIsoString(toValue.getTextString()))); + members.add(Recipient.external(context, Util.toIsoString(toValue.getTextString())).getId()); } } if (retrieved.getCc() != null) { for (EncodedStringValue ccValue : retrieved.getCc()) { - members.add(Address.fromExternal(context, Util.toIsoString(ccValue.getTextString()))); + members.add(Recipient.external(context, Util.toIsoString(ccValue.getTextString())).getId()); } } members.add(from); - members.add(Address.fromExternal(context, TextSecurePreferences.getLocalNumber(context))); + members.add(Recipient.self().getId()); if (retrieved.getBody() != null) { body = PartParser.getMessageText(retrieved.getBody()); @@ -244,7 +235,8 @@ public class MmsDownloadJob extends BaseJob { } if (members.size() > 2) { - group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true))); + List recipients = new ArrayList<>(members); + group = Optional.of(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipients, true)); } IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false, false); diff --git a/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java index 1dee3ff330..aa45930ea7 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java @@ -101,7 +101,7 @@ public class MmsReceiveJob extends BaseJob { private boolean isBlocked(GenericPdu pdu) { if (pdu.getFrom() != null && pdu.getFrom().getTextString() != null) { - Recipient recipients = Recipient.from(context, Address.fromExternal(context, Util.toIsoString(pdu.getFrom().getTextString())), false); + Recipient recipients = Recipient.external(context, Util.toIsoString(pdu.getFrom().getTextString())); return recipients.isBlocked(); } diff --git a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 1362f45957..15c83b7879 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.Hex; -import org.thoughtcrime.securesms.util.NumberUtil; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -221,7 +221,7 @@ public final class MmsSendJob extends SendJob { { SendReq req = new SendReq(); String lineNumber = getMyNumber(context); - Address destination = message.getRecipient().getAddress(); + Address destination = message.getRecipient().requireAddress(); MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId()); List scaledAttachments = message.getAttachments(); @@ -236,9 +236,9 @@ public final class MmsSendJob extends SendJob { for (Recipient member : members) { if (message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST) { - req.addBcc(new EncodedStringValue(member.getAddress().serialize())); + req.addBcc(new EncodedStringValue(member.requireAddress().serialize())); } else { - req.addTo(new EncodedStringValue(member.getAddress().serialize())); + req.addTo(new EncodedStringValue(member.requireAddress().serialize())); } } } else { diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java index 3bb0944208..ddb5b8f994 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -73,10 +73,10 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob { Recipient recipient; while ((recipient = reader.getNext()) != null) { - if (recipient.isGroupRecipient()) { - blockedGroups.add(GroupUtil.getDecodedId(recipient.getAddress().toGroupString())); + if (recipient.isGroup()) { + blockedGroups.add(GroupUtil.getDecodedId(recipient.requireAddress().toGroupString())); } else { - blockedIndividuals.add(recipient.getAddress().serialize()); + blockedIndividuals.add(recipient.requireAddress().serialize()); } } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index f6d8cd4ee3..b77292638a 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.util.guava.Optional; @@ -55,48 +56,46 @@ public class MultiDeviceContactUpdateJob extends BaseJob { private static final long FULL_SYNC_TIME = TimeUnit.HOURS.toMillis(6); - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private static final String KEY_FORCE_SYNC = "force_sync"; - private @Nullable String address; + private @Nullable RecipientId recipientId; private boolean forceSync; - public MultiDeviceContactUpdateJob(@NonNull Context context) { - this(context, false); + public MultiDeviceContactUpdateJob() { + this(false); } - public MultiDeviceContactUpdateJob(@NonNull Context context, boolean forceSync) { - this(context, null, forceSync); + public MultiDeviceContactUpdateJob(boolean forceSync) { + this(null, forceSync); } - public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address) { - this(context, address, true); + public MultiDeviceContactUpdateJob(@Nullable RecipientId recipientId) { + this(recipientId, true); } - public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address, boolean forceSync) { + public MultiDeviceContactUpdateJob(@Nullable RecipientId recipientId, boolean forceSync) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue("MultiDeviceContactUpdateJob") .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), - address, + recipientId, forceSync); } - private MultiDeviceContactUpdateJob(@NonNull Job.Parameters parameters, @Nullable Address address, boolean forceSync) { + private MultiDeviceContactUpdateJob(@NonNull Job.Parameters parameters, @Nullable RecipientId recipientId, boolean forceSync) { super(parameters); - this.forceSync = forceSync; - - if (address != null) this.address = address.serialize(); - else this.address = null; + this.recipientId = recipientId; + this.forceSync = forceSync; } @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_ADDRESS, address) + return new Data.Builder().putString(KEY_RECIPIENT, recipientId != null ? recipientId.serialize() : null) .putBoolean(KEY_FORCE_SYNC, forceSync) .build(); } @@ -115,22 +114,22 @@ public class MultiDeviceContactUpdateJob extends BaseJob { return; } - if (address == null) generateFullContactUpdate(); - else generateSingleContactUpdate(Address.fromSerialized(address)); + if (recipientId == null) generateFullContactUpdate(); + else generateSingleContactUpdate(recipientId); } - private void generateSingleContactUpdate(@NonNull Address address) + private void generateSingleContactUpdate(@NonNull RecipientId recipientId) throws IOException, UntrustedIdentityException, NetworkException { File contactDataFile = createTempFile("multidevice-contact-update"); try { DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); - Recipient recipient = Recipient.from(context, address, false); - Optional identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(address); + Recipient recipient = Recipient.resolved(recipientId); + Optional identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord); - out.write(new DeviceContact(address.toPhoneString(), + out.write(new DeviceContact(recipient.requireAddress().toPhoneString(), Optional.fromNullable(recipient.getName()), getAvatar(recipient.getContactUri()), Optional.fromNullable(recipient.getColor().serialize()), @@ -181,9 +180,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob { for (ContactData contactData : contacts) { Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactData.id)); - Address address = Address.fromExternal(context, contactData.numbers.get(0).number); - Recipient recipient = Recipient.from(context, address, false); - Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(address); + Recipient recipient = Recipient.external(context, contactData.numbers.get(0).number); + Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); Optional verified = getVerifiedMessage(recipient, identity); Optional name = Optional.fromNullable(contactData.name); Optional color = Optional.of(recipient.getColor().serialize()); @@ -191,11 +189,11 @@ public class MultiDeviceContactUpdateJob extends BaseJob { boolean blocked = recipient.isBlocked(); Optional expireTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); - out.write(new DeviceContact(address.toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); + out.write(new DeviceContact(recipient.requireAddress().toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); } if (ProfileKeyUtil.hasProfileKey(context)) { - Recipient self = Recipient.from(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), false); + Recipient self = Recipient.self(); out.write(new DeviceContact(TextSecurePreferences.getLocalNumber(context), Optional.absent(), Optional.absent(), Optional.of(self.getColor().serialize()), Optional.absent(), @@ -302,7 +300,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { private Optional getVerifiedMessage(Recipient recipient, Optional identity) throws InvalidNumberException { if (!identity.isPresent()) return Optional.absent(); - String destination = recipient.getAddress().toPhoneString(); + String destination = recipient.requireAddress().toPhoneString(); IdentityKey identityKey = identity.get().getIdentityKey(); VerifiedMessage.VerifiedState state; @@ -334,8 +332,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob { public static final class Factory implements Job.Factory { @Override public @NonNull MultiDeviceContactUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { - String serialized = data.getString(KEY_ADDRESS); - Address address = serialized != null ? Address.fromSerialized(serialized) : null; + String serialized = data.getString(KEY_RECIPIENT); + RecipientId address = serialized != null ? RecipientId.from(serialized) : null; return new MultiDeviceContactUpdateJob(parameters, address, data.getBoolean(KEY_FORCE_SYNC)); } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index ecacdc1cd0..1c744c00a2 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -4,7 +4,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -13,6 +12,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; @@ -84,11 +84,12 @@ public class MultiDeviceGroupUpdateJob extends BaseJob { if (!record.isMms()) { List members = new LinkedList<>(); - for (Address member : record.getMembers()) { - members.add(member.serialize()); + for (RecipientId member : record.getMembers()) { + members.add(Recipient.resolved(member).requireAddress().serialize()); } - Recipient recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(record.getId(), record.isMms())), false); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(record.getId(), record.isMms())); + Recipient recipient = Recipient.resolved(recipientId); Optional expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()), diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index 300006b1c4..246bcc2d55 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -5,7 +5,6 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; import com.fasterxml.jackson.annotation.JsonProperty; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -14,6 +13,8 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -53,7 +54,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob { this.messageIds = new LinkedList<>(); for (SyncMessageId messageId : messageIds) { - this.messageIds.add(new SerializableSyncMessageId(messageId.getAddress().toPhoneString(), messageId.getTimetamp())); + this.messageIds.add(new SerializableSyncMessageId(messageId.getRecipientId().serialize(), messageId.getTimetamp())); } } @@ -87,7 +88,8 @@ public class MultiDeviceReadUpdateJob extends BaseJob { List readMessages = new LinkedList<>(); for (SerializableSyncMessageId messageId : messageIds) { - readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp)); + Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId)); + readMessages.add(new ReadMessage(recipient.requireAddress().serialize(), messageId.timestamp)); } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); @@ -109,14 +111,14 @@ public class MultiDeviceReadUpdateJob extends BaseJob { private static final long serialVersionUID = 1L; @JsonProperty - private final String sender; + private final String recipientId; @JsonProperty private final long timestamp; - private SerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) { - this.sender = sender; - this.timestamp = timestamp; + private SerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) { + this.recipientId = recipientId; + this.timestamp = timestamp; } } @@ -131,11 +133,10 @@ public class MultiDeviceReadUpdateJob extends BaseJob { throw new AssertionError(e); } }) - .map(id -> new SyncMessageId(Address.fromSerialized(id.sender), id.timestamp)) + .map(id -> new SyncMessageId(RecipientId.from(id.recipientId), id.timestamp)) .toList(); return new MultiDeviceReadUpdateJob(parameters, ids); - } } } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java index 0b1803ca8f..1c7a4efae5 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java @@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -37,12 +38,12 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { private static final String KEY_VERIFIED_STATUS = "verified_status"; private static final String KEY_TIMESTAMP = "timestamp"; - private String destination; + private RecipientId destination; private byte[] identityKey; private VerifiedStatus verifiedStatus; private long timestamp; - public MultiDeviceVerifiedUpdateJob(Address destination, IdentityKey identityKey, VerifiedStatus verifiedStatus) { + public MultiDeviceVerifiedUpdateJob(@NonNull RecipientId destination, IdentityKey identityKey, VerifiedStatus verifiedStatus) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue("__MULTI_DEVICE_VERIFIED_UPDATE__") @@ -56,14 +57,14 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { } private MultiDeviceVerifiedUpdateJob(@NonNull Job.Parameters parameters, - @NonNull Address destination, + @NonNull RecipientId destination, @NonNull byte[] identityKey, @NonNull VerifiedStatus verifiedStatus, long timestamp) { super(parameters); - this.destination = destination.serialize(); + this.destination = destination; this.identityKey = identityKey; this.verifiedStatus = verifiedStatus; this.timestamp = timestamp; @@ -71,7 +72,7 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_DESTINATION, destination) + return new Data.Builder().putString(KEY_DESTINATION, destination.serialize()) .putString(KEY_IDENTITY_KEY, Base64.encodeBytes(identityKey)) .putInt(KEY_VERIFIED_STATUS, verifiedStatus.toInt()) .putLong(KEY_TIMESTAMP, timestamp) @@ -97,12 +98,13 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - Address canonicalDestination = Address.fromSerialized(destination); + Recipient recipient = Recipient.resolved(destination); + Address canonicalDestination = recipient.requireAddress(); VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage), - UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(destination), false))); + UnidentifiedAccessUtil.getAccessFor(context, recipient)); } catch (InvalidKeyException e) { throw new IOException(e); } @@ -135,7 +137,7 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { @Override public @NonNull MultiDeviceVerifiedUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { try { - Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION)); + RecipientId destination = RecipientId.from(data.getString(KEY_DESTINATION)); VerifiedStatus verifiedStatus = VerifiedStatus.forState(data.getInt(KEY_VERIFIED_STATUS)); long timestamp = data.getLong(KEY_TIMESTAMP); byte[] identityKey = Base64.decode(data.getString(KEY_IDENTITY_KEY)); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java index ed33fe5ebf..1433665eae 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java @@ -5,13 +5,14 @@ import androidx.annotation.NonNull; import com.fasterxml.jackson.annotation.JsonProperty; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -45,7 +46,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { private MultiDeviceViewOnceOpenJob(@NonNull Parameters parameters, @NonNull SyncMessageId syncMessageId) { super(parameters); - this.messageId = new SerializableSyncMessageId(syncMessageId.getAddress().toPhoneString(), syncMessageId.getTimetamp()); + this.messageId = new SerializableSyncMessageId(syncMessageId.getRecipientId().serialize(), syncMessageId.getTimetamp()); } @Override @@ -74,7 +75,8 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(messageId.sender, messageId.timestamp); + Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId)); + ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(recipient.requireAddress().serialize(), messageId.timestamp); messageSender.sendMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context)); } @@ -94,14 +96,14 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { private static final long serialVersionUID = 1L; @JsonProperty - private final String sender; + private final String recipientId; @JsonProperty private final long timestamp; - private SerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) { - this.sender = sender; - this.timestamp = timestamp; + private SerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) { + this.recipientId = recipientId; + this.timestamp = timestamp; } } @@ -116,7 +118,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { throw new AssertionError(e); } - SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(messageId.sender), messageId.timestamp); + SyncMessageId syncMessageId = new SyncMessageId(RecipientId.from(messageId.recipientId), messageId.timestamp); return new MultiDeviceViewOnceOpenJob(parameters, syncMessageId); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index c9b345c98d..ce0e10cf47 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -28,7 +28,6 @@ import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ConversationListActivity; -import org.thoughtcrime.securesms.IncomingMessageProcessor; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; @@ -81,6 +80,7 @@ import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; @@ -287,7 +287,7 @@ public class PushDecryptJob extends BaseJob { Log.w(TAG, "Got unrecognized message..."); } - resetRecipientToPush(Recipient.from(context, Address.fromExternal(context, content.getSender()), false)); + resetRecipientToPush(Recipient.external(context, content.getSender())); if (envelope.isPreKeySignalMessage()) { ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob()); @@ -337,7 +337,7 @@ public class PushDecryptJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.external(context, content.getSender()).getId()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp()); @@ -353,7 +353,7 @@ public class PushDecryptJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.external(context, content.getSender()).getId()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); context.startService(intent); @@ -367,7 +367,7 @@ public class PushDecryptJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.external(context, content.getSender()).getId()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex()); @@ -387,7 +387,7 @@ public class PushDecryptJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.external(context, content.getSender()).getId()); context.startService(intent); } @@ -399,7 +399,7 @@ public class PushDecryptJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.external(context, content.getSender()).getId()); context.startService(intent); } @@ -408,7 +408,7 @@ public class PushDecryptJob extends BaseJob { @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), "", Optional.absent(), 0, @@ -445,9 +445,9 @@ public class PushDecryptJob extends BaseJob { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - if (!recipient.isGroupRecipient()) { + if (!recipient.isGroup()) { SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(recipient.getAddress().toPhoneString()); + sessionStore.deleteAllSessions(recipient.requireAddress().toPhoneString()); SecurityEvent.broadcastSecurityUpdateEvent(context); @@ -481,7 +481,7 @@ public class PushDecryptJob extends BaseJob { { ApplicationContext.getInstance(context) .getJobManager() - .add(new RequestGroupInfoJob(content.getSender(), group.getGroupId())); + .add(new RequestGroupInfoJob(Recipient.external(context, content.getSender()).getId(), group.getGroupId())); } private void handleExpirationUpdate(@NonNull SignalServiceContent content, @@ -492,7 +492,7 @@ public class PushDecryptJob extends BaseJob { try { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); Recipient recipient = getMessageDestination(content, message); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(recipient.getId(), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, true, false, @@ -507,7 +507,7 @@ public class PushDecryptJob extends BaseJob { database.insertSecureDecryptedMessageInbox(mediaMessage, -1); - DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getExpiresInSeconds()); + DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient.getId(), message.getExpiresInSeconds()); if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); @@ -574,12 +574,12 @@ public class PushDecryptJob extends BaseJob { if (message.getMessage().getProfileKey().isPresent()) { Recipient recipient = null; - if (message.getDestination().isPresent()) recipient = Recipient.from(context, Address.fromExternal(context, message.getDestination().get()), false); - else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false); + if (message.getDestination().isPresent()) recipient = Recipient.external(context, message.getDestination().get()); + else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.external(context, GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)); if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true); } } @@ -599,7 +599,7 @@ public class PushDecryptJob extends BaseJob { if (message.isContactsRequest()) { ApplicationContext.getInstance(context) .getJobManager() - .add(new MultiDeviceContactUpdateJob(context, true)); + .add(new MultiDeviceContactUpdateJob(true)); ApplicationContext.getInstance(context) .getJobManager() @@ -635,8 +635,8 @@ public class PushDecryptJob extends BaseJob { private void handleSynchronizeReadMessage(@NonNull List readMessages, long envelopeTimestamp) { for (ReadMessage readMessage : readMessages) { - List> expiringText = DatabaseFactory.getSmsDatabase(context).setTimestampRead(new SyncMessageId(Address.fromExternal(context, readMessage.getSender()), readMessage.getTimestamp()), envelopeTimestamp); - List> expiringMedia = DatabaseFactory.getMmsDatabase(context).setTimestampRead(new SyncMessageId(Address.fromExternal(context, readMessage.getSender()), readMessage.getTimestamp()), envelopeTimestamp); + List> expiringText = DatabaseFactory.getSmsDatabase(context).setTimestampRead(new SyncMessageId(Recipient.external(context, readMessage.getSender()).getId(), readMessage.getTimestamp()), envelopeTimestamp); + List> expiringMedia = DatabaseFactory.getMmsDatabase(context).setTimestampRead(new SyncMessageId(Recipient.external(context, readMessage.getSender()).getId(), readMessage.getTimestamp()), envelopeTimestamp); for (Pair expiringMessage : expiringText) { ApplicationContext.getInstance(context) @@ -657,7 +657,7 @@ public class PushDecryptJob extends BaseJob { } private void handleSynchronizeViewOnceOpenMessage(@NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp) { - Address author = Address.fromExternal(context, openMessage.getSender()); + RecipientId author = Recipient.external(context, openMessage.getSender()).getId(); long timestamp = openMessage.getTimestamp(); MessageRecord record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(timestamp, author); @@ -687,7 +687,7 @@ public class PushDecryptJob extends BaseJob { Optional> sharedContacts = getContacts(message.getSharedContacts()); Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional sticker = getStickerAttachment(message.getSticker()); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Recipient.external(context, content.getSender()).getId(), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, message.isViewOnce(), @@ -749,7 +749,7 @@ public class PushDecryptJob extends BaseJob { database.markAsSent(messageId, true); - DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getMessage().getExpiresInSeconds()); + DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient.getId(), message.getMessage().getExpiresInSeconds()); return threadId; } @@ -793,12 +793,12 @@ public class PushDecryptJob extends BaseJob { try { long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); - if (recipients.getAddress().isGroup()) { - updateGroupReceiptStatus(message, messageId, recipients.getAddress().toGroupString()); + if (recipients.requireAddress().isGroup()) { + updateGroupReceiptStatus(message, messageId, recipients.requireAddress().toGroupString()); } database.markAsSent(messageId, true); - database.markUnidentified(messageId, message.isUnidentified(recipients.getAddress().serialize())); + database.markUnidentified(messageId, message.isUnidentified(recipients.requireAddress().serialize())); List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId); List stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList(); @@ -822,7 +822,7 @@ public class PushDecryptJob extends BaseJob { } if (recipients.isLocalNumber()) { - SyncMessageId id = new SyncMessageId(recipients.getAddress(), message.getTimestamp()); + SyncMessageId id = new SyncMessageId(recipients.getId(), message.getTimestamp()); DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); } @@ -837,14 +837,13 @@ public class PushDecryptJob extends BaseJob { private void handleGroupRecipientUpdate(@NonNull SentTranscriptMessage message) { Recipient recipient = getSyncMessageDestination(message); - if (!recipient.isGroupRecipient()) { + if (!recipient.isGroup()) { Log.w(TAG, "Got recipient update for a non-group message! Skipping."); return; } MmsSmsDatabase database = DatabaseFactory.getMmsSmsDatabase(context); - MessageRecord record = database.getMessageFor(message.getTimestamp(), - Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); + MessageRecord record = database.getMessageFor(message.getTimestamp(), Recipient.self().getId()); if (record == null) { Log.w(TAG, "Got recipient update for non-existing message! Skipping."); @@ -856,26 +855,27 @@ public class PushDecryptJob extends BaseJob { return; } - updateGroupReceiptStatus(message, record.getId(), recipient.getAddress().toGroupString()); + updateGroupReceiptStatus(message, record.getId(), recipient.requireAddress().toGroupString()); } private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull String groupString) { - GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupString, false); - Map localReceipts = Stream.of(receiptDatabase.getGroupReceiptInfo(messageId)) - .collect(Collectors.toMap(info -> info.getAddress().serialize(), GroupReceiptInfo::getStatus)); + GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); + List messageRecipients = Stream.of(message.getRecipients()).map(address -> Recipient.external(context, address)).toList(); + List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupString, false); + Map localReceipts = Stream.of(receiptDatabase.getGroupReceiptInfo(messageId)) + .collect(Collectors.toMap(GroupReceiptInfo::getRecipientId, GroupReceiptInfo::getStatus)); - for (String address : message.getRecipients()) { + for (Recipient messageRecipient : messageRecipients) { //noinspection ConstantConditions - if (localReceipts.containsKey(address) && localReceipts.get(address) < GroupReceiptDatabase.STATUS_UNDELIVERED) { - receiptDatabase.update(Address.fromSerialized(address), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); - } else if (!localReceipts.containsKey(address)) { - receiptDatabase.insert(Collections.singletonList(Address.fromSerialized(address)), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); + if (localReceipts.containsKey(messageRecipient.getId()) && localReceipts.get(messageRecipient.getId()) < GroupReceiptDatabase.STATUS_UNDELIVERED) { + receiptDatabase.update(messageRecipient.getId(), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); + } else if (!localReceipts.containsKey(messageRecipient.getId())) { + receiptDatabase.insert(Collections.singletonList(messageRecipient.getId()), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); } } for (Recipient member : members) { - receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize())); + receiptDatabase.setUnidentified(member.getId(), messageId, message.isUnidentified(member.requireAddress().serialize())); } } @@ -899,7 +899,7 @@ public class PushDecryptJob extends BaseJob { } else { notifyTypingStoppedFromIncomingMessage(recipient, content.getSender(), content.getSenderDevice()); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), + IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), @@ -932,7 +932,7 @@ public class PushDecryptJob extends BaseJob { } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - boolean isGroup = recipient.getAddress().isGroup(); + boolean isGroup = recipient.requireAddress().isGroup(); MessagingDatabase database; long messageId; @@ -944,13 +944,13 @@ public class PushDecryptJob extends BaseJob { messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); database = DatabaseFactory.getMmsDatabase(context); - updateGroupReceiptStatus(message, messageId, recipient.getAddress().toGroupString()); + updateGroupReceiptStatus(message, messageId, recipient.requireAddress().toGroupString()); } else { OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis); messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null); database = DatabaseFactory.getSmsDatabase(context); - database.markUnidentified(messageId, message.isUnidentified(recipient.getAddress().serialize())); + database.markUnidentified(messageId, message.isUnidentified(recipient.requireAddress().serialize())); } database.markAsSent(messageId, true); @@ -963,7 +963,7 @@ public class PushDecryptJob extends BaseJob { } if (recipient.isLocalNumber()) { - SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getTimestamp()); + SyncMessageId id = new SyncMessageId(recipient.getId(), message.getTimestamp()); DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); } @@ -1098,13 +1098,12 @@ public class PushDecryptJob extends BaseJob { private void handleProfileKey(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { - RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); - Address sourceAddress = Address.fromExternal(context, content.getSender()); - Recipient recipient = Recipient.from(context, sourceAddress, false); + RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); + Recipient recipient = Recipient.external(context, content.getSender()); if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { - database.setProfileKey(recipient, message.getProfileKey().get()); - database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN); + database.setProfileKey(recipient.getId(), message.getProfileKey().get()); + database.setUnidentifiedAccessMode(recipient.getId(), RecipientDatabase.UnidentifiedAccessMode.UNKNOWN); ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileJob(recipient)); } } @@ -1114,7 +1113,7 @@ public class PushDecryptJob extends BaseJob { { ApplicationContext.getInstance(context) .getJobManager() - .add(new SendDeliveryReceiptJob(Address.fromExternal(context, content.getSender()), message.getTimestamp())); + .add(new SendDeliveryReceiptJob(Recipient.external(context, content.getSender()).getId(), message.getTimestamp())); } @SuppressLint("DefaultLocale") @@ -1124,7 +1123,7 @@ public class PushDecryptJob extends BaseJob { for (long timestamp : message.getTimestamps()) { Log.i(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), System.currentTimeMillis()); + .incrementDeliveryReceiptCount(new SyncMessageId(Recipient.external(context, content.getSender()).getId(), timestamp), System.currentTimeMillis()); } } @@ -1137,7 +1136,7 @@ public class PushDecryptJob extends BaseJob { Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), content.getTimestamp()); + .incrementReadReceiptCount(new SyncMessageId(Recipient.external(context, content.getSender()).getId(), timestamp), content.getTimestamp()); } } } @@ -1149,13 +1148,13 @@ public class PushDecryptJob extends BaseJob { return; } - Recipient author = Recipient.from(context, Address.fromExternal(context, content.getSender()), false); + Recipient author = Recipient.external(context, content.getSender()); long threadId; if (typingMessage.getGroupId().isPresent()) { - Address groupAddress = Address.fromExternal(context, GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false)); - Recipient groupRecipient = Recipient.from(context, groupAddress, false); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false)); + Recipient groupRecipient = Recipient.resolved(recipientId); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); } else { @@ -1200,7 +1199,7 @@ public class PushDecryptJob extends BaseJob { return Optional.absent(); } - Address author = Address.fromExternal(context, quote.get().getAuthor().getNumber()); + RecipientId author = Recipient.external(context, quote.get().getAuthor().getNumber()).getId(); MessageRecord message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quote.get().getId(), author); if (message != null) { @@ -1315,7 +1314,7 @@ public class PushDecryptJob extends BaseJob { private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional group) { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender), + IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, sender).getId(), senderDevice, timestamp, "", group, 0, false); @@ -1325,22 +1324,22 @@ public class PushDecryptJob extends BaseJob { private Recipient getSyncMessageDestination(SentTranscriptMessage message) { if (message.getMessage().getGroupInfo().isPresent()) { - return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false); + return Recipient.external(context, GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)); } else { - return Recipient.from(context, Address.fromExternal(context, message.getDestination().get()), false); + return Recipient.external(context, message.getDestination().get()); } } private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { if (message.getGroupInfo().isPresent()) { - return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); + return Recipient.external(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)); } else { - return Recipient.from(context, Address.fromExternal(context, content.getSender()), false); + return Recipient.external(context, content.getSender()); } } private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) { - Recipient author = Recipient.from(context, Address.fromExternal(context, sender), false); + Recipient author = Recipient.external(context, sender); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient); if (threadId > 0) { @@ -1355,15 +1354,15 @@ public class PushDecryptJob extends BaseJob { return true; } - Recipient sender = Recipient.from(context, Address.fromExternal(context, content.getSender()), false); + Recipient sender = Recipient.external(context, content.getSender()); if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); Recipient conversation = getMessageDestination(content, message); - if (conversation.isGroupRecipient() && conversation.isBlocked()) { + if (conversation.isGroup() && conversation.isBlocked()) { return true; - } else if (conversation.isGroupRecipient()) { + } else if (conversation.isGroup()) { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); Optional groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)) : Optional.absent(); @@ -1392,7 +1391,7 @@ public class PushDecryptJob extends BaseJob { private void resetRecipientToPush(@NonNull Recipient recipient) { if (recipient.isForceSmsSelection()) { - DatabaseFactory.getRecipientDatabase(context).setForceSmsSelection(recipient, false); + DatabaseFactory.getRecipientDatabase(context).setForceSmsSelection(recipient.getId(), false); } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 989a39b59c..8b71249fb5 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.GroupUtil; @@ -57,32 +58,37 @@ public class PushGroupSendJob extends PushSendJob { private static final String TAG = PushGroupSendJob.class.getSimpleName(); - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_FILTER_ADDRESS = "filter_address"; + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_FILTER_RECIPIENT = "filter_recipient"; - private long messageId; - private String filterAddress; + private long messageId; + private RecipientId filterRecipient; - public PushGroupSendJob(long messageId, @NonNull Address destination, @Nullable Address filterAddress) { + public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @Nullable RecipientId filterRecipient) { this(new Job.Parameters.Builder() - .setQueue(destination.toGroupString()) + .setQueue(destination.toQueueKey()) .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), - messageId, filterAddress); + messageId, filterRecipient); } - private PushGroupSendJob(@NonNull Job.Parameters parameters, long messageId, @Nullable Address filterAddress) { + private PushGroupSendJob(@NonNull Job.Parameters parameters, long messageId, @Nullable RecipientId filterRecipient) { super(parameters); - this.messageId = messageId; - this.filterAddress = filterAddress == null ? null :filterAddress.toPhoneString(); + this.messageId = messageId; + this.filterRecipient = filterRecipient; } @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination, @Nullable Address filterAddress) { + public static void enqueue(@NonNull Context context, + @NonNull JobManager jobManager, + long messageId, + @NonNull RecipientId destination, + @Nullable RecipientId filterAddress) + { try { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); OutgoingMediaMessage message = database.getOutgoingMessage(messageId); @@ -101,7 +107,7 @@ public class PushGroupSendJob extends PushSendJob { @Override public @NonNull Data serialize() { return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId) - .putString(KEY_FILTER_ADDRESS, filterAddress) + .putString(KEY_FILTER_RECIPIENT, filterRecipient != null ? filterRecipient.serialize() : null) .build(); } @@ -132,18 +138,18 @@ public class PushGroupSendJob extends PushSendJob { try { log(TAG, "Sending message: " + messageId); - List
target; + List target; - if (filterAddress != null) target = Collections.singletonList(Address.fromSerialized(filterAddress)); - else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(NetworkFailure::getAddress).toList(); - else target = getGroupMessageRecipients(message.getRecipient().getAddress().toGroupString(), messageId); + if (filterRecipient != null) target = Collections.singletonList(Recipient.resolved(filterRecipient).getId()); + else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).toList(); + else target = getGroupMessageRecipients(message.getRecipient().requireAddress().toGroupString(), messageId); List results = deliver(message, target); - List networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList(); - List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList(); - Set
successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet()); - List resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList(); - List resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList(); + List networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Recipient.external(context, result.getAddress().getNumber()).getId())).toList(); + List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Recipient.external(context, result.getAddress().getNumber()).getId(), result.getIdentityFailure().getIdentityKey())).toList(); + Set successIds = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> result.getAddress().getNumber()).map(a -> Recipient.external(context, a).getId()).collect(Collectors.toSet()); + List resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); + List resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); List successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList(); for (NetworkFailure resolvedFailure : resolvedNetworkFailures) { @@ -152,7 +158,7 @@ public class PushGroupSendJob extends PushSendJob { } for (IdentityKeyMismatch resolvedIdentity : resolvedIdentityFailures) { - database.removeMismatchedIdentity(messageId, resolvedIdentity.getAddress(), resolvedIdentity.getIdentityKey()); + database.removeMismatchedIdentity(messageId, resolvedIdentity.getRecipientId(context), resolvedIdentity.getIdentityKey()); existingIdentityMismatches.remove(resolvedIdentity); } @@ -161,11 +167,11 @@ public class PushGroupSendJob extends PushSendJob { } for (IdentityKeyMismatch mismatch : identityMismatches) { - database.addMismatchedIdentity(messageId, mismatch.getAddress(), mismatch.getIdentityKey()); + database.addMismatchedIdentity(messageId, mismatch.getRecipientId(context), mismatch.getIdentityKey()); } for (SendMessageResult success : successes) { - DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.fromSerialized(success.getAddress().getNumber()), + DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Recipient.external(context, success.getAddress().getNumber()).getId(), messageId, success.getSuccess().isUnidentified()); } @@ -210,25 +216,24 @@ public class PushGroupSendJob extends PushSendJob { DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); } - private List deliver(OutgoingMediaMessage message, @NonNull List
destinations) + private List deliver(OutgoingMediaMessage message, @NonNull List destinations) throws IOException, UntrustedIdentityException, UndeliverableMessageException { rotateSenderCertificateIfNecessary(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - String groupId = message.getRecipient().getAddress().toGroupString(); + String groupId = message.getRecipient().requireAddress().toGroupString(); Optional profileKey = getProfileKey(message.getRecipient()); Optional quote = getQuoteFor(message); Optional sticker = getStickerFor(message); List sharedContacts = getSharedContactsFor(message); List previews = getPreviewsFor(message); - List addresses = Stream.of(destinations).map(this::getPushAddress).toList(); + List addresses = Stream.of(destinations).map(Recipient::resolved).map(Recipient::requireAddress).map(this::getPushAddress).toList(); List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); List attachmentPointers = getAttachmentPointersFor(attachments); boolean isRecipientUpdate = destinations.size() != DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId).size(); List> unidentifiedAccess = Stream.of(addresses) - .map(address -> Address.fromSerialized(address.getNumber())) - .map(address -> Recipient.from(context, address, false)) + .map(address -> Recipient.external(context, address.getNumber())) .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)) .toList(); @@ -266,19 +271,19 @@ public class PushGroupSendJob extends PushSendJob { } } - private @NonNull List
getGroupMessageRecipients(String groupId, long messageId) { + private @NonNull List getGroupMessageRecipients(String groupId, long messageId) { List destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); - if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList(); + if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).toList(); List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false); - return Stream.of(members).map(Recipient::getAddress).toList(); + return Stream.of(members).map(Recipient::getId).toList(); } public static class Factory implements Job.Factory { @Override public @NonNull PushGroupSendJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) { - String address = data.getString(KEY_FILTER_ADDRESS); - Address filter = address != null ? Address.fromSerialized(data.getString(KEY_FILTER_ADDRESS)) : null; + String raw = data.getString(KEY_FILTER_RECIPIENT); + RecipientId filter = raw != null ? RecipientId.from(raw) : null; return new PushGroupSendJob(parameters, data.getLong(KEY_MESSAGE_ID), filter); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index 1c964ed6f1..7551fad9d5 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -4,7 +4,6 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; @@ -14,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -41,10 +41,10 @@ public class PushGroupUpdateJob extends BaseJob { private static final String KEY_SOURCE = "source"; private static final String KEY_GROUP_ID = "group_id"; - private String source; - private byte[] groupId; + private RecipientId source; + private byte[] groupId; - public PushGroupUpdateJob(String source, byte[] groupId) { + public PushGroupUpdateJob(@NonNull RecipientId source, byte[] groupId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) @@ -54,7 +54,7 @@ public class PushGroupUpdateJob extends BaseJob { groupId); } - private PushGroupUpdateJob(@NonNull Job.Parameters parameters, String source, byte[] groupId) { + private PushGroupUpdateJob(@NonNull Job.Parameters parameters, RecipientId source, byte[] groupId) { super(parameters); this.source = source; @@ -63,7 +63,7 @@ public class PushGroupUpdateJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_SOURCE, source) + return new Data.Builder().putString(KEY_SOURCE, source.serialize()) .putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)) .build(); } @@ -94,8 +94,8 @@ public class PushGroupUpdateJob extends BaseJob { List members = new LinkedList<>(); - for (Address member : record.get().getMembers()) { - members.add(member.serialize()); + for (RecipientId member : record.get().getMembers()) { + members.add(Recipient.resolved(member).requireAddress().serialize()); } SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE) @@ -105,8 +105,8 @@ public class PushGroupUpdateJob extends BaseJob { .withName(record.get().getTitle()) .build(); - Address groupAddress = Address.fromSerialized(GroupUtil.getEncodedId(groupId, false)); - Recipient groupRecipient = Recipient.from(context, groupAddress, false); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(groupId, false)); + Recipient groupRecipient = Recipient.resolved(groupRecipientId); SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() .asGroupMessage(groupContext) @@ -115,8 +115,10 @@ public class PushGroupUpdateJob extends BaseJob { .build(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - messageSender.sendMessage(new SignalServiceAddress(source), - UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(source), false)), + Recipient recipient = Recipient.resolved(source); + + messageSender.sendMessage(new SignalServiceAddress(recipient.requireAddress().serialize()), + UnidentifiedAccessUtil.getAccessFor(context, recipient), message); } @@ -128,7 +130,6 @@ public class PushGroupUpdateJob extends BaseJob { @Override public void onCanceled() { - } public static final class Factory implements Job.Factory { @@ -136,7 +137,7 @@ public class PushGroupUpdateJob extends BaseJob { public @NonNull PushGroupUpdateJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) { try { return new PushGroupUpdateJob(parameters, - data.getString(KEY_SOURCE), + RecipientId.from(data.getString(KEY_SOURCE)), GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID))); } catch (IOException e) { throw new AssertionError(e); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index bbaeae0132..9e022b46f3 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -55,8 +55,8 @@ public class PushMediaSendJob extends PushSendJob { private long messageId; - public PushMediaSendJob(long messageId, Address destination) { - this(constructParameters(destination), messageId); + public PushMediaSendJob(long messageId, @NonNull Recipient recipient) { + this(constructParameters(recipient), messageId); } private PushMediaSendJob(Job.Parameters parameters, long messageId) { @@ -65,9 +65,9 @@ public class PushMediaSendJob extends PushSendJob { } @WorkerThread - public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) { + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Recipient recipient) { try { - if (!destination.isPhone()) { + if (!recipient.requireAddress().isPhone()) { throw new AssertionError(); } @@ -75,7 +75,7 @@ public class PushMediaSendJob extends PushSendJob { OutgoingMediaMessage message = database.getOutgoingMessage(messageId); JobManager.Chain compressAndUploadAttachment = createCompressingAndUploadAttachmentsChain(jobManager, message); - compressAndUploadAttachment.then(new PushMediaSendJob(messageId, destination)) + compressAndUploadAttachment.then(new PushMediaSendJob(messageId, recipient)) .enqueue(); } catch (NoSuchMessageException | MmsException e) { @@ -128,7 +128,7 @@ public class PushMediaSendJob extends PushSendJob { database.markUnidentified(messageId, unidentified); if (recipient.isLocalNumber()) { - SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getSentTimeMillis()); + SyncMessageId id = new SyncMessageId(recipient.getId(), message.getSentTimeMillis()); DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); } @@ -136,13 +136,13 @@ public class PushMediaSendJob extends PushSendJob { if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) { if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) { log(TAG, "Marking recipient as UD-unrestricted following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED); } else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) { log(TAG, "Marking recipient as UD-enabled following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED); } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) { log(TAG, "Marking recipient as UD-disabled following a non-UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED); } } @@ -164,7 +164,7 @@ public class PushMediaSendJob extends PushSendJob { ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); } catch (UntrustedIdentityException uie) { warn(TAG, "Failure", uie); - database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); + database.addMismatchedIdentity(messageId, Recipient.external(context, uie.getE164Number()).getId(), uie.getIdentityKey()); database.markAsSentFailed(messageId); } } @@ -189,7 +189,7 @@ public class PushMediaSendJob extends PushSendJob { throw new UndeliverableMessageException("No destination address."); } - final Address destination = message.getRecipient().getAddress(); + final Address destination = message.getRecipient().requireAddress(); if (!destination.isPhone()) { if (destination.isEmail()) throw new UndeliverableMessageException("Not e164, is email"); diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index 3cb4a9597c..10e911adb4 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; @@ -69,9 +70,9 @@ public abstract class PushSendJob extends SendJob { super(parameters); } - protected static Job.Parameters constructParameters(Address destination) { + protected static Job.Parameters constructParameters(@NonNull Recipient recipient) { return new Parameters.Builder() - .setQueue(destination.serialize()) + .setQueue(recipient.getId().toQueueKey()) .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) @@ -223,7 +224,7 @@ public abstract class PushSendJob extends SendJob { long quoteId = message.getOutgoingQuote().getId(); String quoteBody = message.getOutgoingQuote().getText(); - Address quoteAuthor = message.getOutgoingQuote().getAuthor(); + RecipientId quoteAuthor = message.getOutgoingQuote().getAuthor(); List quoteAttachments = new LinkedList<>(); for (Attachment attachment : message.getOutgoingQuote().getAttachments()) { @@ -259,7 +260,7 @@ public abstract class PushSendJob extends SendJob { } } - return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments)); + return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(Recipient.resolved(quoteAuthor).requireAddress().serialize()), quoteBody, quoteAttachments)); } protected Optional getStickerFor(OutgoingMediaMessage message) { diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index d757f2af19..bca01b2d94 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -42,8 +42,8 @@ public class PushTextSendJob extends PushSendJob { private long messageId; - public PushTextSendJob(long messageId, Address destination) { - this(constructParameters(destination), messageId); + public PushTextSendJob(long messageId, @NonNull Recipient recipient) { + this(constructParameters(recipient), messageId); } private PushTextSendJob(@NonNull Job.Parameters parameters, long messageId) { @@ -90,7 +90,7 @@ public class PushTextSendJob extends PushSendJob { database.markUnidentified(messageId, unidentified); if (recipient.isLocalNumber()) { - SyncMessageId id = new SyncMessageId(recipient.getAddress(), record.getDateSent()); + SyncMessageId id = new SyncMessageId(recipient.getId(), record.getDateSent()); DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); } @@ -98,13 +98,13 @@ public class PushTextSendJob extends PushSendJob { if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) { if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) { log(TAG, "Marking recipient as UD-unrestricted following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED); } else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) { log(TAG, "Marking recipient as UD-enabled following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED); } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) { log(TAG, "Marking recipient as UD-disabled following a non-UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED); } } @@ -122,7 +122,7 @@ public class PushTextSendJob extends PushSendJob { ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); } catch (UntrustedIdentityException e) { warn(TAG, "Failure", e); - database.addMismatchedIdentity(record.getId(), Address.fromSerialized(e.getE164Number()), e.getIdentityKey()); + database.addMismatchedIdentity(record.getId(), Recipient.external(context, e.getE164Number()).getId(), e.getIdentityKey()); database.markAsSentFailed(record.getId()); database.markAsPush(record.getId()); } @@ -154,7 +154,7 @@ public class PushTextSendJob extends PushSendJob { rotateSenderCertificateIfNecessary(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); + SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().requireAddress()); Optional profileKey = getProfileKey(message.getIndividualRecipient()); Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, message.getIndividualRecipient()); diff --git a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index 878dc44f2b..4ec3558720 100644 --- a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -31,10 +32,10 @@ public class RequestGroupInfoJob extends BaseJob { private static final String KEY_SOURCE = "source"; private static final String KEY_GROUP_ID = "group_id"; - private String source; - private byte[] groupId; + private RecipientId source; + private byte[] groupId; - public RequestGroupInfoJob(@NonNull String source, @NonNull byte[] groupId) { + public RequestGroupInfoJob(@NonNull RecipientId source, @NonNull byte[] groupId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) @@ -45,7 +46,7 @@ public class RequestGroupInfoJob extends BaseJob { } - private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull String source, @NonNull byte[] groupId) { + private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull RecipientId source, @NonNull byte[] groupId) { super(parameters); this.source = source; @@ -54,7 +55,7 @@ public class RequestGroupInfoJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_SOURCE, source) + return new Data.Builder().putString(KEY_SOURCE, source.serialize()) .putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)) .build(); } @@ -76,8 +77,10 @@ public class RequestGroupInfoJob extends BaseJob { .build(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - messageSender.sendMessage(new SignalServiceAddress(source), - UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromExternal(context, source), false)), + Recipient recipient = Recipient.resolved(source); + + messageSender.sendMessage(new SignalServiceAddress(recipient.requireAddress().serialize()), + UnidentifiedAccessUtil.getAccessFor(context, recipient), message); } @@ -97,7 +100,7 @@ public class RequestGroupInfoJob extends BaseJob { public @NonNull RequestGroupInfoJob create(@NonNull Parameters parameters, @NonNull Data data) { try { return new RequestGroupInfoJob(parameters, - data.getString(KEY_SOURCE), + RecipientId.from(data.getString(KEY_SOURCE)), GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID))); } catch (IOException e) { throw new AssertionError(e); diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index 27ce9002b8..0e4ea1d9e7 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -1,11 +1,9 @@ package org.thoughtcrime.securesms.jobs; -import android.app.Application; import androidx.annotation.NonNull; import android.text.TextUtils; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -15,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; @@ -35,14 +34,14 @@ public class RetrieveProfileAvatarJob extends BaseJob { private static final int MAX_PROFILE_SIZE_BYTES = 20 * 1024 * 1024; private static final String KEY_PROFILE_AVATAR = "profile_avatar"; - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private String profileAvatar; private Recipient recipient; public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) { this(new Job.Parameters.Builder() - .setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize()) + .setQueue("RetrieveProfileAvatarJob::" + recipient.getId().toQueueKey()) .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.HOURS.toMillis(1)) .setMaxInstances(1) @@ -61,7 +60,7 @@ public class RetrieveProfileAvatarJob extends BaseJob { @Override public @NonNull Data serialize() { return new Data.Builder().putString(KEY_PROFILE_AVATAR, profileAvatar) - .putString(KEY_ADDRESS, recipient.getAddress().serialize()) + .putString(KEY_RECIPIENT, recipient.getId().serialize()) .build(); } @@ -86,9 +85,9 @@ public class RetrieveProfileAvatarJob extends BaseJob { } if (TextUtils.isEmpty(profileAvatar)) { - Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.getAddress().serialize()); - AvatarHelper.delete(context, recipient.getAddress()); - database.setProfileAvatar(recipient, profileAvatar); + Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.requireAddress().serialize()); + AvatarHelper.delete(context, recipient.requireAddress()); + database.setProfileAvatar(recipient.getId(), profileAvatar); return; } @@ -105,11 +104,11 @@ public class RetrieveProfileAvatarJob extends BaseJob { throw new IOException("Failed to copy stream. Likely a Conscrypt issue.", e); } - decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress())); + decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.requireAddress())); } catch (PushNetworkException e) { if (e.getCause() instanceof NonSuccessfulResponseCodeException) { - Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.getAddress().serialize()); - AvatarHelper.delete(context, recipient.getAddress()); + Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.requireAddress().serialize()); + AvatarHelper.delete(context, recipient.requireAddress()); } else { throw e; } @@ -117,7 +116,7 @@ public class RetrieveProfileAvatarJob extends BaseJob { if (downloadDestination != null) downloadDestination.delete(); } - database.setProfileAvatar(recipient, profileAvatar); + database.setProfileAvatar(recipient.getId(), profileAvatar); } @Override @@ -132,16 +131,10 @@ public class RetrieveProfileAvatarJob extends BaseJob { public static final class Factory implements Job.Factory { - private final Application application; - - public Factory(Application application) { - this.application = application; - } - @Override public @NonNull RetrieveProfileAvatarJob create(@NonNull Parameters parameters, @NonNull Data data) { return new RetrieveProfileAvatarJob(parameters, - Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true), + Recipient.resolved(RecipientId.from(data.getString(KEY_RECIPIENT))), data.getString(KEY_PROFILE_AVATAR)); } } diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 2f49d4dadc..8834fac673 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.jobs; -import android.app.Application; import androidx.annotation.NonNull; import android.text.TextUtils; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; @@ -17,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.IdentityUtil; @@ -43,7 +42,7 @@ public class RetrieveProfileJob extends BaseJob { private static final String TAG = RetrieveProfileJob.class.getSimpleName(); - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private final Recipient recipient; @@ -62,7 +61,7 @@ public class RetrieveProfileJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_ADDRESS, recipient.getAddress().serialize()).build(); + return new Data.Builder().putString(KEY_RECIPIENT, recipient.getId().serialize()).build(); } @Override @@ -72,8 +71,8 @@ public class RetrieveProfileJob extends BaseJob { @Override public void onRun() throws IOException { - if (recipient.isGroupRecipient()) handleGroupRecipient(recipient); - else handleIndividualRecipient(recipient); + if (recipient.isGroup()) handleGroupRecipient(recipient); + else handleIndividualRecipient(recipient); } @Override @@ -85,12 +84,12 @@ public class RetrieveProfileJob extends BaseJob { public void onCanceled() {} private void handleIndividualRecipient(Recipient recipient) throws IOException { - if (recipient.getAddress().isPhone()) handlePhoneNumberRecipient(recipient); + if (recipient.requireAddress().isPhone()) handlePhoneNumberRecipient(recipient); else Log.w(TAG, "Skipping fetching profile of non-phone recipient"); } private void handlePhoneNumberRecipient(Recipient recipient) throws IOException { - String number = recipient.getAddress().toPhoneString(); + String number = recipient.requireAddress().toPhoneString(); Optional unidentifiedAccess = getUnidentifiedAccess(recipient); SignalServiceProfile profile; @@ -112,7 +111,7 @@ public class RetrieveProfileJob extends BaseJob { } private void handleGroupRecipient(Recipient group) throws IOException { - List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.getAddress().toGroupString(), false); + List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.requireAddress().toGroupString(), false); for (Recipient recipient : recipients) { handleIndividualRecipient(recipient); @@ -149,14 +148,14 @@ public class RetrieveProfileJob extends BaseJob { IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyValue), 0); if (!DatabaseFactory.getIdentityDatabase(context) - .getIdentity(recipient.getAddress()) + .getIdentity(recipient.getId()) .isPresent()) { Log.w(TAG, "Still first use..."); return; } - IdentityUtil.saveIdentity(context, recipient.getAddress().toPhoneString(), identityKey); + IdentityUtil.saveIdentity(context, recipient.requireAddress().toPhoneString(), identityKey); } catch (InvalidKeyException | IOException e) { Log.w(TAG, e); } @@ -168,10 +167,10 @@ public class RetrieveProfileJob extends BaseJob { if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) { Log.i(TAG, "Marking recipient UD status as unrestricted."); - recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED); + recipientDatabase.setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED); } else if (profileKey == null || unidentifiedAccessVerifier == null) { Log.i(TAG, "Marking recipient UD status as disabled."); - recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED); + recipientDatabase.setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED); } else { ProfileCipher profileCipher = new ProfileCipher(profileKey); boolean verifiedUnidentifiedAccess; @@ -185,7 +184,7 @@ public class RetrieveProfileJob extends BaseJob { UnidentifiedAccessMode mode = verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED; Log.i(TAG, "Marking recipient UD status as " + mode.name() + " after verification."); - recipientDatabase.setUnidentifiedAccessMode(recipient, mode); + recipientDatabase.setUnidentifiedAccessMode(recipient.getId(), mode); } } @@ -202,7 +201,7 @@ public class RetrieveProfileJob extends BaseJob { } if (!Util.equals(plaintextProfileName, recipient.getProfileName())) { - DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient, plaintextProfileName); + DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient.getId(), plaintextProfileName); } } catch (InvalidCiphertextException | IOException e) { Log.w(TAG, e); @@ -231,15 +230,9 @@ public class RetrieveProfileJob extends BaseJob { public static final class Factory implements Job.Factory { - private final Application application; - - public Factory(Application application) { - this.application = application; - } - @Override public @NonNull RetrieveProfileJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new RetrieveProfileJob(parameters, Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true)); + return new RetrieveProfileJob(parameters, Recipient.resolved(RecipientId.from(data.getString(KEY_RECIPIENT)))); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index 1570c1a36a..0256cdd6dd 100644 --- a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.profiles.AvatarHelper; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; @@ -72,8 +73,7 @@ public class RotateProfileKeyJob extends BaseJob { private @Nullable StreamDetails getProfileAvatar() { try { - Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)); - File avatarFile = AvatarHelper.getAvatarFile(context, localAddress); + File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().requireAddress()); if (avatarFile.exists()) { return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length()); diff --git a/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java index b82fcec798..210016f1d2 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; @@ -25,42 +26,42 @@ public class SendDeliveryReceiptJob extends BaseJob { public static final String KEY = "SendDeliveryReceiptJob"; - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private static final String KEY_MESSAGE_ID = "message_id"; private static final String KEY_TIMESTAMP = "timestamp"; private static final String TAG = SendReadReceiptJob.class.getSimpleName(); - private String address; - private long messageId; - private long timestamp; + private RecipientId recipientId; + private long messageId; + private long timestamp; - public SendDeliveryReceiptJob(@NonNull Address address, long messageId) { + public SendDeliveryReceiptJob(@NonNull RecipientId recipientId, long messageId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), - address, + recipientId, messageId, System.currentTimeMillis()); } private SendDeliveryReceiptJob(@NonNull Job.Parameters parameters, - @NonNull Address address, + @NonNull RecipientId recipientId, long messageId, long timestamp) { super(parameters); - this.address = address.serialize(); - this.messageId = messageId; - this.timestamp = timestamp; + this.recipientId = recipientId; + this.messageId = messageId; + this.timestamp = timestamp; } @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_ADDRESS, address) + return new Data.Builder().putString(KEY_RECIPIENT, recipientId.serialize()) .putLong(KEY_MESSAGE_ID, messageId) .putLong(KEY_TIMESTAMP, timestamp) .build(); @@ -74,13 +75,14 @@ public class SendDeliveryReceiptJob extends BaseJob { @Override public void onRun() throws IOException, UntrustedIdentityException { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - SignalServiceAddress remoteAddress = new SignalServiceAddress(address); + Recipient recipient = Recipient.resolved(recipientId); + SignalServiceAddress remoteAddress = new SignalServiceAddress(recipient.requireAddress().serialize()); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, Collections.singletonList(messageId), timestamp); messageSender.sendReceipt(remoteAddress, - UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)), + UnidentifiedAccessUtil.getAccessFor(context, recipient), receiptMessage); } @@ -92,14 +94,14 @@ public class SendDeliveryReceiptJob extends BaseJob { @Override public void onCanceled() { - Log.w(TAG, "Failed to send delivery receipt to: " + address); + Log.w(TAG, "Failed to send delivery receipt to: " + recipientId); } public static final class Factory implements Job.Factory { @Override public @NonNull SendDeliveryReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) { return new SendDeliveryReceiptJob(parameters, - Address.fromSerialized(data.getString(KEY_ADDRESS)), + RecipientId.from(data.getString(KEY_RECIPIENT)), data.getLong(KEY_MESSAGE_ID), data.getLong(KEY_TIMESTAMP)); } diff --git a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java index ade1d41653..0d916f486e 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java @@ -1,16 +1,18 @@ package org.thoughtcrime.securesms.jobs; +import android.app.Application; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -30,34 +32,35 @@ public class SendReadReceiptJob extends BaseJob { private static final String TAG = SendReadReceiptJob.class.getSimpleName(); private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient"; private static final String KEY_MESSAGE_IDS = "message_ids"; private static final String KEY_TIMESTAMP = "timestamp"; - private String address; - private List messageIds; - private long timestamp; + private RecipientId recipientId; + private List messageIds; + private long timestamp; - public SendReadReceiptJob(Address address, List messageIds) { + public SendReadReceiptJob(@NonNull RecipientId recipientId, List messageIds) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), - address, + recipientId, messageIds, System.currentTimeMillis()); } private SendReadReceiptJob(@NonNull Job.Parameters parameters, - @NonNull Address address, + @NonNull RecipientId recipientId, @NonNull List messageIds, long timestamp) { super(parameters); - this.address = address.serialize(); - this.messageIds = messageIds; - this.timestamp = timestamp; + this.recipientId = recipientId; + this.messageIds = messageIds; + this.timestamp = timestamp; } @Override @@ -67,7 +70,7 @@ public class SendReadReceiptJob extends BaseJob { ids[i] = messageIds.get(i); } - return new Data.Builder().putString(KEY_ADDRESS, address) + return new Data.Builder().putString(KEY_RECIPIENT, recipientId.serialize()) .putLongArray(KEY_MESSAGE_IDS, ids) .putLong(KEY_TIMESTAMP, timestamp) .build(); @@ -82,12 +85,13 @@ public class SendReadReceiptJob extends BaseJob { public void onRun() throws IOException, UntrustedIdentityException { if (!TextSecurePreferences.isReadReceiptsEnabled(context) || messageIds.isEmpty()) return; + Recipient recipient = Recipient.resolved(recipientId); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - SignalServiceAddress remoteAddress = new SignalServiceAddress(address); + SignalServiceAddress remoteAddress = new SignalServiceAddress(recipient.requireAddress().serialize()); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); messageSender.sendReceipt(remoteAddress, - UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)), + UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)), receiptMessage); } @@ -99,22 +103,30 @@ public class SendReadReceiptJob extends BaseJob { @Override public void onCanceled() { - Log.w(TAG, "Failed to send read receipts to: " + address); + Log.w(TAG, "Failed to send read receipts to: " + recipientId); } public static final class Factory implements Job.Factory { + + private final Application application; + + public Factory(@NonNull Application application) { + this.application = application; + } + @Override public @NonNull SendReadReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) { - Address address = Address.fromSerialized(data.getString(KEY_ADDRESS)); - long timestamp = data.getLong(KEY_TIMESTAMP); - long[] ids = data.hasLongArray(KEY_MESSAGE_IDS) ? data.getLongArray(KEY_MESSAGE_IDS) : new long[0]; - List messageIds = new ArrayList<>(ids.length); + long timestamp = data.getLong(KEY_TIMESTAMP); + long[] ids = data.hasLongArray(KEY_MESSAGE_IDS) ? data.getLongArray(KEY_MESSAGE_IDS) : new long[0]; + List messageIds = new ArrayList<>(ids.length); + RecipientId recipientId = data.hasString(KEY_RECIPIENT) ? RecipientId.from(data.getString(KEY_RECIPIENT)) + : Recipient.external(application, data.getString(KEY_ADDRESS)).getId(); for (long id : ids) { messageIds.add(id); } - return new SendReadReceiptJob(parameters, address, messageIds, timestamp); + return new SendReadReceiptJob(parameters, recipientId, messageIds, timestamp); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java index 19a317e219..0589ab1087 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java @@ -100,7 +100,7 @@ public class SmsReceiveJob extends BaseJob { private boolean isBlocked(IncomingTextMessage message) { if (message.getSender() != null) { - Recipient recipient = Recipient.from(context, message.getSender(), false); + Recipient recipient = Recipient.resolved(message.getSender()); return recipient.isBlocked(); } @@ -134,7 +134,9 @@ public class SmsReceiveJob extends BaseJob { List messages = new LinkedList<>(); for (Object pdu : pdus) { - messages.add(new IncomingTextMessage(context, SmsMessage.createFromPdu((byte[])pdu), subscriptionId)); + SmsMessage message = SmsMessage.createFromPdu((byte[])pdu); + Recipient recipient = Recipient.external(context, message.getDisplayOriginatingAddress()); + messages.add(new IncomingTextMessage(recipient.getId(), message, subscriptionId)); } if (messages.isEmpty()) { diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java index a4eae4bb8a..2d500a6076 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java @@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.SmsDeliveryListener; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.thoughtcrime.securesms.util.NumberUtil; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.ArrayList; @@ -40,11 +40,11 @@ public class SmsSendJob extends SendJob { private long messageId; private int runAttempt; - public SmsSendJob(Context context, long messageId, @NonNull Address destination) { + public SmsSendJob(Context context, long messageId, @NonNull Recipient destination) { this(context, messageId, destination, 0); } - public SmsSendJob(Context context, long messageId, @NonNull Address destination, int runAttempt) { + public SmsSendJob(Context context, long messageId, @NonNull Recipient destination, int runAttempt) { this(constructParameters(context, destination), messageId, runAttempt); } @@ -118,7 +118,7 @@ public class SmsSendJob extends SendJob { throw new UndeliverableMessageException("Trying to send a secure SMS?"); } - String recipient = message.getIndividualRecipient().getAddress().serialize(); + String recipient = message.getIndividualRecipient().requireAddress().serialize(); // See issue #1516 for bug report, and discussion on commits related to #4833 for problems // related to the original fix to #1516. This still may not be a correct fix if networks allow @@ -227,12 +227,12 @@ public class SmsSendJob extends SendJob { } } - private static Job.Parameters constructParameters(@NonNull Context context, @NonNull Address destination) { + private static Job.Parameters constructParameters(@NonNull Context context, @NonNull Recipient destination) { String constraint = TextSecurePreferences.isWifiSmsEnabled(context) ? NetworkOrCellServiceConstraint.KEY : CellServiceConstraint.KEY; return new Job.Parameters.Builder() .setMaxAttempts(MAX_ATTEMPTS) - .setQueue(destination.serialize()) + .setQueue(destination.getId().toQueueKey()) .addConstraint(constraint) .build(); } diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java index 85ac9b1c63..e30cdc2c98 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java @@ -104,7 +104,7 @@ public class SmsSentJob extends BaseJob { Log.w(TAG, "Service connectivity problem, requeuing..."); ApplicationContext.getInstance(context) .getJobManager() - .add(new SmsSendJob(context, messageId, record.getIndividualRecipient().getAddress(), runAttempt + 1)); + .add(new SmsSendJob(context, messageId, record.getIndividualRecipient(), runAttempt + 1)); break; default: database.markAsSentFailed(messageId); diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 8106dbdd9e..aa75383b70 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -84,13 +84,13 @@ public class TypingSendJob extends BaseJob { List recipients = Collections.singletonList(recipient); Optional groupId = Optional.absent(); - if (recipient.isGroupRecipient()) { - recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false); - groupId = Optional.of(GroupUtil.getDecodedId(recipient.getAddress().toGroupString())); + if (recipient.isGroup()) { + recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireAddress().toGroupString(), false); + groupId = Optional.of(GroupUtil.getDecodedId(recipient.requireAddress().toGroupString())); } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - List addresses = Stream.of(recipients).map(r -> new SignalServiceAddress(r.getAddress().serialize())).toList(); + List addresses = Stream.of(recipients).map(r -> new SignalServiceAddress(r.requireAddress().serialize())).toList(); List> unidentifiedAccess = Stream.of(recipients).map(r -> UnidentifiedAccessUtil.getAccessFor(context, r)).toList(); SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); diff --git a/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java b/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java index d58c352385..48026aeeee 100644 --- a/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java +++ b/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java @@ -25,10 +25,10 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.components.ConversationItemFooter; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -36,11 +36,11 @@ import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.views.Stub; -public class LongMessageActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener { +public class LongMessageActivity extends PassphraseRequiredActionBarActivity { - private static final String KEY_ADDRESS = "address"; - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_IS_MMS = "is_mms"; + private static final String KEY_CONVERSATION_RECIPIENT = "recipient_id"; + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_IS_MMS = "is_mms"; private static final int MAX_DISPLAY_LENGTH = 64 * 1024; @@ -52,9 +52,9 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity imp private LongMessageViewModel viewModel; - public static Intent getIntent(@NonNull Context context, @NonNull Address conversationAddress, long messageId, boolean isMms) { + public static Intent getIntent(@NonNull Context context, @NonNull RecipientId conversationRecipient, long messageId, boolean isMms) { Intent intent = new Intent(context, LongMessageActivity.class); - intent.putExtra(KEY_ADDRESS, conversationAddress.serialize()); + intent.putExtra(KEY_CONVERSATION_RECIPIENT, conversationRecipient); intent.putExtra(KEY_MESSAGE_ID, messageId); intent.putExtra(KEY_IS_MMS, isMms); return intent; @@ -77,9 +77,9 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity imp initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1), getIntent().getBooleanExtra(KEY_IS_MMS, false)); - Recipient conversationRecipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true); - conversationRecipient.addListener(this); - updateActionBarColor(conversationRecipient.getColor()); + LiveRecipient conversationRecipient = Recipient.live(getIntent().getParcelableExtra(KEY_CONVERSATION_RECIPIENT)); + conversationRecipient.observe(this, recipient -> updateActionBarColor(recipient.getColor())); + updateActionBarColor(conversationRecipient.get().getColor()); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @@ -104,11 +104,6 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity imp return false; } - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> updateActionBarColor(recipient.getColor())); - } - private void updateActionBarColor(@NonNull MaterialColor color) { getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this))); @@ -135,7 +130,7 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity imp getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_your_message)); } else { Recipient recipient = message.get().getMessageRecord().getRecipient(); - String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getAddress().serialize()) ; + String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.requireAddress().serialize()) ; getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name)); } diff --git a/src/org/thoughtcrime/securesms/mediasend/CameraContactAdapter.java b/src/org/thoughtcrime/securesms/mediasend/CameraContactAdapter.java index 1859d08505..7f78f0dfd3 100644 --- a/src/org/thoughtcrime/securesms/mediasend/CameraContactAdapter.java +++ b/src/org/thoughtcrime/securesms/mediasend/CameraContactAdapter.java @@ -172,7 +172,7 @@ class CameraContactAdapter extends SectionedRecyclerViewAdapter callback) { @@ -66,7 +69,7 @@ class CameraContactsRepository { try (ThreadDatabase.Reader threadReader = threadDatabase.readerFor(threadDatabase.getRecentPushConversationList(RECENT_MAX))) { ThreadRecord threadRecord; while ((threadRecord = threadReader.getNext()) != null) { - recipients.add(threadRecord.getRecipient()); + recipients.add(threadRecord.getRecipient().resolve()); } } @@ -79,8 +82,8 @@ class CameraContactsRepository { try (Cursor cursor = contactsDatabase.queryTextSecureContacts(query)) { while (cursor.moveToNext()) { - Address address = Address.fromExternal(context, cursor.getString(1)); - recipients.add(Recipient.from(context, address, false)); + Recipient recipient = Recipient.external(context, cursor.getString(1)); + recipients.add(recipient); } } @@ -98,7 +101,8 @@ class CameraContactsRepository { try (GroupDatabase.Reader reader = groupDatabase.getGroupsFilteredByTitle(query)) { GroupDatabase.GroupRecord groupRecord; while ((groupRecord = reader.getNext()) != null) { - recipients.add(Recipient.from(context, Address.fromSerialized(groupRecord.getEncodedId()), false)); + RecipientId recipientId = recipientDatabase.getOrInsertFromGroupId(groupRecord.getEncodedId()); + recipients.add(Recipient.resolved(recipientId)); } } diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 481c01ac80..d3d8ba4511 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; import org.thoughtcrime.securesms.components.emoji.EmojiToggle; import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.imageeditor.model.EditorModel; import org.thoughtcrime.securesms.logging.Log; @@ -59,7 +58,9 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.scribbles.ImageEditorFragment; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState; @@ -106,7 +107,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple public static final String EXTRA_VIEW_ONCE = "view_once"; - private static final String KEY_ADDRESS = "address"; + private static final String KEY_RECIPIENT = "recipient_id"; private static final String KEY_BODY = "body"; private static final String KEY_MEDIA = "media"; private static final String KEY_TRANSPORT = "transport"; @@ -118,7 +119,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple private static final String TAG_CAMERA = "camera"; private static final String TAG_CONTACTS = "contacts"; - private @Nullable Recipient recipient; + private @Nullable LiveRecipient recipient; private TransportOption transport; private MediaSendViewModel viewModel; @@ -150,7 +151,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple */ public static Intent buildGalleryIntent(@NonNull Context context, @NonNull Recipient recipient, @NonNull String body, @NonNull TransportOption transport) { Intent intent = new Intent(context, MediaSendActivity.class); - intent.putExtra(KEY_ADDRESS, recipient.getAddress()); + intent.putExtra(KEY_RECIPIENT, recipient.getId()); intent.putExtra(KEY_TRANSPORT, transport); intent.putExtra(KEY_BODY, body); return intent; @@ -214,16 +215,16 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple mediaRail = findViewById(R.id.mediasend_media_rail); emojiDrawer = new Stub<>(findViewById(R.id.mediasend_emoji_drawer_stub)); - Address address = getIntent().getParcelableExtra(KEY_ADDRESS); - if (address != null) { - recipient = Recipient.from(this, address, true); + RecipientId recipientId = getIntent().getParcelableExtra(KEY_RECIPIENT); + if (recipientId != null) { + recipient = Recipient.live(recipientId); } viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(getApplication(), new MediaRepository())).get(MediaSendViewModel.class); transport = getIntent().getParcelableExtra(KEY_TRANSPORT); viewModel.setTransport(transport); - viewModel.setRecipient(recipient); + viewModel.setRecipient(recipient != null ? recipient.get() : null); viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY)); List media = getIntent().getParcelableArrayListExtra(KEY_MEDIA); @@ -243,7 +244,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND) .commit(); } else { - MediaPickerFolderFragment fragment = MediaPickerFolderFragment.newInstance(this, recipient); + MediaPickerFolderFragment fragment = MediaPickerFolderFragment.newInstance(this, recipient != null ? recipient.get() : null); getSupportFragmentManager().beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_FOLDER_PICKER) .commit(); @@ -304,17 +305,12 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple composeText.append(viewModel.getBody()); - if (recipient == null) { - composeText.setHint(R.string.MediaSendActivity_message); - } else if (recipient.isLocalNumber()) { - composeText.setHint(getString(R.string.note_to_self), null); - } else { - String displayName = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName()) - .or(recipient.getAddress().serialize())); - composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null); + if (recipient != null) { + recipient.observe(this, this::presentRecipient); } + presentRecipient(recipient != null ? recipient.get() : null); + composeText.setOnEditorActionListener((v, actionId, event) -> { boolean isSend = actionId == EditorInfo.IME_ACTION_SEND; if (isSend) sendButton.performClick(); @@ -427,7 +423,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple @Override public void onGalleryClicked() { - MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(this, recipient); + MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(this, recipient != null ? recipient.get() : null); getSupportFragmentManager().beginTransaction() .replace(R.id.mediasend_fragment_container, folderFragment, TAG_FOLDER_PICKER) @@ -505,7 +501,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple public void onAddMediaClicked(@NonNull String bucketId) { // TODO: Get actual folder title somehow - MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(this, recipient); + MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(this, recipient != null ? recipient.get() : null); MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", viewModel.getMaxSelection()); getSupportFragmentManager().beginTransaction() @@ -650,6 +646,20 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple }); } + private void presentRecipient(@Nullable Recipient recipient) { + if (recipient == null) { + composeText.setHint(R.string.MediaSendActivity_message); + } else if (recipient.isLocalNumber()) { + composeText.setHint(getString(R.string.note_to_self), null); + } else { + String displayName = Optional.fromNullable(recipient.getName()) + .or(Optional.fromNullable(recipient.getProfileName()) + .or(recipient.requireAddress().serialize())); + composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null); + } + + } + private void navigateToMediaSend(@NonNull Locale locale) { MediaSendFragment fragment = MediaSendFragment.newInstance(locale); String backstackTag = null; diff --git a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 3a99fcd13d..f5a8b554dc 100644 --- a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -38,7 +38,8 @@ public class ApplicationMigrations { private static final MutableLiveData UI_BLOCKING_MIGRATION_RUNNING = new MutableLiveData<>(); private static final class Version { - static final int LEGACY = 455; + static final int LEGACY = 455; + static final int RECIPIENT_ID = 525; // TODO [greyson] USE PROPER APPLICATION VERSION } /** @@ -128,6 +129,10 @@ public class ApplicationMigrations { jobs.add(new LegacyMigrationJob()); } + if (lastSeenVersion < Version.RECIPIENT_ID) { + jobs.add(new DatabaseMigrationJob()); + } + return jobs; } } diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 2a623ca71c..676f49dea4 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -1,10 +1,13 @@ package org.thoughtcrime.securesms.mms; +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.linkpreview.LinkPreview; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -16,24 +19,24 @@ import java.util.List; public class IncomingMediaMessage { - private final Address from; - private final Address groupId; - private final String body; - private final boolean push; - private final long sentTimeMillis; - private final int subscriptionId; - private final long expiresIn; - private final boolean expirationUpdate; - private final QuoteModel quote; - private final boolean unidentified; - private final boolean viewOnce; + private final RecipientId from; + private final String groupId; + private final String body; + private final boolean push; + private final long sentTimeMillis; + private final int subscriptionId; + private final long expiresIn; + private final boolean expirationUpdate; + private final QuoteModel quote; + private final boolean unidentified; + private final boolean viewOnce; private final List attachments = new LinkedList<>(); private final List sharedContacts = new LinkedList<>(); private final List linkPreviews = new LinkedList<>(); - public IncomingMediaMessage(Address from, - Optional
groupId, + public IncomingMediaMessage(@NonNull RecipientId from, + Optional groupId, String body, long sentTimeMillis, List attachments, @@ -58,7 +61,7 @@ public class IncomingMediaMessage { this.attachments.addAll(attachments); } - public IncomingMediaMessage(Address from, + public IncomingMediaMessage(@NonNull RecipientId from, long sentTimeMillis, int subscriptionId, long expiresIn, @@ -84,7 +87,7 @@ public class IncomingMediaMessage { this.quote = quote.orNull(); this.unidentified = unidentified; - if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); + if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId(), false); else this.groupId = null; this.attachments.addAll(PointerAttachment.forPointers(attachments)); @@ -108,11 +111,11 @@ public class IncomingMediaMessage { return attachments; } - public Address getFrom() { + public @NonNull RecipientId getFrom() { return from; } - public Address getGroupId() { + public String getGroupId() { return groupId; } diff --git a/src/org/thoughtcrime/securesms/mms/QuoteId.java b/src/org/thoughtcrime/securesms/mms/QuoteId.java index 929c527600..9a45e00214 100644 --- a/src/org/thoughtcrime/securesms/mms/QuoteId.java +++ b/src/org/thoughtcrime/securesms/mms/QuoteId.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.mms; +import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -8,6 +10,8 @@ import org.json.JSONObject; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; /** * Represents the information required to find the {@link MessageRecord} pointed to by a quote. @@ -16,13 +20,14 @@ public class QuoteId { private static final String TAG = QuoteId.class.getSimpleName(); - private static final String ID = "id"; - private static final String AUTHOR = "author"; + private static final String ID = "id"; + private static final String AUTHOR_DEPRECATED = "author"; + private static final String AUTHOR = "author_id"; - private final long id; - private final Address author; + private final long id; + private final RecipientId author; - public QuoteId(long id, @NonNull Address author) { + public QuoteId(long id, @NonNull RecipientId author) { this.id = id; this.author = author; } @@ -31,7 +36,7 @@ public class QuoteId { return id; } - public @NonNull Address getAuthor() { + public @NonNull RecipientId getAuthor() { return author; } @@ -47,10 +52,13 @@ public class QuoteId { } } - public static @Nullable QuoteId deserialize(@NonNull String serialized) { + public static @Nullable QuoteId deserialize(@NonNull Context context, @NonNull String serialized) { try { - JSONObject json = new JSONObject(serialized); - return new QuoteId(json.getLong(ID), Address.fromSerialized(json.getString(AUTHOR))); + JSONObject json = new JSONObject(serialized); + RecipientId id = json.has(AUTHOR) ? RecipientId.from(json.getString(AUTHOR)) + : Recipient.external(context, json.getString(AUTHOR_DEPRECATED)).getId(); + + return new QuoteId(json.getLong(ID), id); } catch (JSONException e) { Log.e(TAG, "Failed to deserialize from json", e); return null; diff --git a/src/org/thoughtcrime/securesms/mms/QuoteModel.java b/src/org/thoughtcrime/securesms/mms/QuoteModel.java index 953f0a5c0c..60acc9dee3 100644 --- a/src/org/thoughtcrime/securesms/mms/QuoteModel.java +++ b/src/org/thoughtcrime/securesms/mms/QuoteModel.java @@ -1,22 +1,24 @@ package org.thoughtcrime.securesms.mms; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.util.List; public class QuoteModel { private final long id; - private final Address author; + private final RecipientId author; private final String text; private final boolean missing; private final List attachments; - public QuoteModel(long id, Address author, String text, boolean missing, @Nullable List attachments) { + public QuoteModel(long id, @NonNull RecipientId author, String text, boolean missing, @Nullable List attachments) { this.id = id; this.author = author; this.text = text; @@ -28,7 +30,7 @@ public class QuoteModel { return id; } - public Address getAuthor() { + public RecipientId getAuthor() { return author; } diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index ab568b80b8..8d001dca36 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -45,7 +45,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { public static final String TAG = AndroidAutoReplyReceiver.class.getSimpleName(); public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"; - public static final String ADDRESS_EXTRA = "car_address"; + public static final String RECIPIENT_EXTRA = "car_recipient"; public static final String VOICE_REPLY_KEY = "car_voice_reply_key"; public static final String THREAD_ID_EXTRA = "car_reply_thread_id"; @@ -59,10 +59,9 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { if (remoteInput == null) return; - final Address address = intent.getParcelableExtra(ADDRESS_EXTRA); final long threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1); final CharSequence responseText = getMessageText(intent); - final Recipient recipient = Recipient.from(context, address, false); + final Recipient recipient = Recipient.resolved(intent.getParcelableExtra(RECIPIENT_EXTRA)); if (responseText != null) { new AsyncTask() { @@ -74,7 +73,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); long expiresIn = recipient.getExpireMessages() * 1000L; - if (recipient.isGroupRecipient()) { + if (recipient.resolve().isGroup()) { Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, false, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); replyThreadId = MessageSender.send(context, reply, threadId, false, null); diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index d7b0cf02f5..70f3c094e9 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import java.util.LinkedList; @@ -79,16 +80,16 @@ public class MarkReadReceiver extends BroadcastReceiver { .getJobManager() .add(new MultiDeviceReadUpdateJob(syncMessageIds)); - Map> addressMap = Stream.of(markedReadMessages) - .map(MarkedMessageInfo::getSyncMessageId) - .collect(Collectors.groupingBy(SyncMessageId::getAddress)); + Map> recipientIdMap = Stream.of(markedReadMessages) + .map(MarkedMessageInfo::getSyncMessageId) + .collect(Collectors.groupingBy(SyncMessageId::getRecipientId)); - for (Address address : addressMap.keySet()) { - List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); + for (Map.Entry> entry : recipientIdMap.entrySet()) { + List timestamps = Stream.of(entry.getValue()).map(SyncMessageId::getTimetamp).toList(); ApplicationContext.getInstance(context) .getJobManager() - .add(new SendReadReceiptJob(address, timestamps)); + .add(new SendReadReceiptJob(entry.getKey(), timestamps)); } } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index c8fef46bca..713e160c00 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -110,7 +110,7 @@ public class MessageNotifier { sendInThreadNotification(context, recipient); } else { Intent intent = new Intent(context, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.setData((Uri.parse("custom://" + System.currentTimeMillis()))); diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 3ae4d5a2a4..2cc0014961 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -99,7 +99,7 @@ public class NotificationChannels { NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); if (!channelExists(notificationManager.getNotificationChannel(recipient.getNotificationChannel()))) { String id = createChannelFor(context, recipient); - db.setNotificationChannel(recipient, id); + db.setNotificationChannel(recipient.getId(), id); } } } @@ -144,9 +144,9 @@ public class NotificationChannels { VibrateState vibrateState = recipient.getMessageVibrate(); boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == VibrateState.ENABLED; Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context); - String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getAddress()); + String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress()); - return createChannelFor(context, recipient.getAddress(), displayName, messageRingtone, vibrationEnabled); + return createChannelFor(context, recipient.requireAddress(), displayName, messageRingtone, vibrationEnabled); } /** @@ -245,7 +245,7 @@ public class NotificationChannels { } public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) { - if (!supported() || recipient.getNotificationChannel() == null) { + if (!supported() || recipient.resolve().getNotificationChannel() == null) { return null; } @@ -287,13 +287,13 @@ public class NotificationChannels { } Log.i(TAG, "Updating recipient message ringtone with URI: " + String.valueOf(uri)); - String newChannelId = generateChannelIdFor(recipient.getAddress()); + String newChannelId = generateChannelIdFor(recipient.requireAddress()); boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context), recipient.getNotificationChannel(), - generateChannelIdFor(recipient.getAddress()), + generateChannelIdFor(recipient.requireAddress()), channel -> channel.setSound(uri == null ? Settings.System.DEFAULT_NOTIFICATION_URI : uri, getRingtoneAudioAttributes())); - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient, success ? newChannelId : null); + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), success ? newChannelId : null); ensureCustomChannelConsistency(context); } @@ -354,13 +354,13 @@ public class NotificationChannels { Log.i(TAG, "Updating recipient vibrate with value: " + vibrateState); boolean enabled = vibrateState == VibrateState.DEFAULT ? getMessageVibrate(context) : vibrateState == VibrateState.ENABLED; - String newChannelId = generateChannelIdFor(recipient.getAddress()); + String newChannelId = generateChannelIdFor(recipient.requireAddress()); boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context), recipient.getNotificationChannel(), newChannelId, channel -> channel.enableVibration(enabled)); - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient, success ? newChannelId : null); + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), success ? newChannelId : null); ensureCustomChannelConsistency(context); } @@ -382,7 +382,7 @@ public class NotificationChannels { } NotificationChannel channel = new NotificationChannel(recipient.getNotificationChannel(), - getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getAddress()), + getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress()), NotificationManager.IMPORTANCE_HIGH); channel.setGroup(CATEGORY_MESSAGES); notificationManager.createNotificationChannel(channel); @@ -415,7 +415,7 @@ public class NotificationChannels { for (Recipient customRecipient : customRecipients) { if (!existingChannelIds.contains(customRecipient.getNotificationChannel())) { - db.setNotificationChannel(customRecipient, null); + db.setNotificationChannel(customRecipient.getId(), null); } } } @@ -515,10 +515,10 @@ public class NotificationChannels { while ((recipient = recipients.getNext()) != null) { assert recipient.getNotificationChannel() != null; - String newChannelId = generateChannelIdFor(recipient.getAddress()); + String newChannelId = generateChannelIdFor(recipient.requireAddress()); boolean success = updateExistingChannel(notificationManager, recipient.getNotificationChannel(), newChannelId, channel -> setLedPreference(channel, color)); - database.setNotificationChannel(recipient, success ? newChannelId : null); + database.setNotificationChannel(recipient.getId(), success ? newChannelId : null); } } diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationItem.java b/src/org/thoughtcrime/securesms/notifications/NotificationItem.java index 7ffa4d1852..987c7f593e 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -69,8 +69,8 @@ public class NotificationItem { public PendingIntent getPendingIntent(Context context) { Intent intent = new Intent(context, ConversationActivity.class); Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; - if (notifyRecipients != null) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, notifyRecipients.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, notifyRecipients.getId()); intent.putExtra("thread_id", threadId); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationState.java b/src/org/thoughtcrime/securesms/notifications/NotificationState.java index 6200ff0382..48b3e3d584 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -124,7 +124,7 @@ public class NotificationState { Intent intent = new Intent(RemoteReplyReceiver.REPLY_ACTION); intent.setClass(context, RemoteReplyReceiver.class); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); - intent.putExtra(RemoteReplyReceiver.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(RemoteReplyReceiver.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod); intent.setPackage(context.getPackageName()); @@ -138,7 +138,7 @@ public class NotificationState { intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); intent.setClass(context, AndroidAutoReplyReceiver.class); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); - intent.putExtra(AndroidAutoReplyReceiver.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(AndroidAutoReplyReceiver.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]); intent.setPackage(context.getPackageName()); @@ -168,7 +168,7 @@ public class NotificationState { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size()); Intent intent = new Intent(context, ConversationPopupActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)threads.toArray()[0]); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 96a3322592..c400f4090d 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -25,11 +25,11 @@ import android.os.AsyncTask; import android.os.Bundle; import androidx.core.app.RemoteInput; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; @@ -43,10 +43,10 @@ import java.util.List; */ public class RemoteReplyReceiver extends BroadcastReceiver { - public static final String TAG = RemoteReplyReceiver.class.getSimpleName(); - public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY"; - public static final String ADDRESS_EXTRA = "address"; - public static final String REPLY_METHOD = "reply_method"; + public static final String TAG = RemoteReplyReceiver.class.getSimpleName(); + public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY"; + public static final String RECIPIENT_EXTRA = "recipient_extra"; + public static final String REPLY_METHOD = "reply_method"; @SuppressLint("StaticFieldLeak") @Override @@ -57,11 +57,11 @@ public class RemoteReplyReceiver extends BroadcastReceiver { if (remoteInput == null) return; - final Address address = intent.getParcelableExtra(ADDRESS_EXTRA); + final RecipientId recipientId = intent.getParcelableExtra(RECIPIENT_EXTRA); final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD); final CharSequence responseText = remoteInput.getCharSequence(MessageNotifier.EXTRA_REMOTE_REPLY); - if (address == null) throw new AssertionError("No address specified"); + if (recipientId == null) throw new AssertionError("No recipientId specified"); if (replyMethod == null) throw new AssertionError("No reply method specified"); if (responseText != null) { @@ -70,9 +70,9 @@ public class RemoteReplyReceiver extends BroadcastReceiver { protected Void doInBackground(Void... params) { long threadId; - Recipient recipient = Recipient.from(context, address, false); - int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); - long expiresIn = recipient.getExpireMessages() * 1000L; + Recipient recipient = Recipient.resolved(recipientId); + int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); + long expiresIn = recipient.getExpireMessages() * 1000L; switch (replyMethod) { case GroupMessage: { diff --git a/src/org/thoughtcrime/securesms/notifications/ReplyMethod.java b/src/org/thoughtcrime/securesms/notifications/ReplyMethod.java index cd1e35c9b8..79dfa0ea78 100644 --- a/src/org/thoughtcrime/securesms/notifications/ReplyMethod.java +++ b/src/org/thoughtcrime/securesms/notifications/ReplyMethod.java @@ -14,7 +14,7 @@ public enum ReplyMethod { UnsecuredSmsMessage; public static @NonNull ReplyMethod forRecipient(Context context, Recipient recipient) { - if (recipient.isGroupRecipient()) { + if (recipient.isGroup()) { return ReplyMethod.GroupMessage; } else if (TextSecurePreferences.isPushRegistered(context) && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED && !recipient.isForceSmsSelection()) { return ReplyMethod.SecureMessage; diff --git a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 207ffd21a7..52724badf8 100644 --- a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -109,7 +109,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipients.isGroupRecipient()) { + if (privacy.isDisplayContact() && threadRecipients.isGroup()) { stringBuilder.append(Util.getBoldedString(individualRecipient.toShortString() + ": ")); } @@ -201,7 +201,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { + if (privacy.isDisplayContact() && threadRecipient.isGroup()) { stringBuilder.append(Util.getBoldedString(individualRecipient.toShortString() + ": ")); } diff --git a/src/org/thoughtcrime/securesms/util/NumberUtil.java b/src/org/thoughtcrime/securesms/phonenumbers/NumberUtil.java similarity index 63% rename from src/org/thoughtcrime/securesms/util/NumberUtil.java rename to src/org/thoughtcrime/securesms/phonenumbers/NumberUtil.java index 9ab4f47567..f0f43ad01f 100644 --- a/src/org/thoughtcrime/securesms/util/NumberUtil.java +++ b/src/org/thoughtcrime/securesms/phonenumbers/NumberUtil.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.thoughtcrime.securesms.util; +package org.thoughtcrime.securesms.phonenumbers; import android.telephony.PhoneNumberUtils; @@ -33,26 +33,4 @@ public class NumberUtil { public static boolean isValidSmsOrEmail(String number) { return PhoneNumberUtils.isWellFormedSmsAddress(number) || isValidEmail(number); } - -// public static boolean isValidSmsOrEmailOrGroup(String number) { -// return PhoneNumberUtils.isWellFormedSmsAddress(number) || -// isValidEmail(number) || -// GroupUtil.isEncodedGroup(number); -// } -// -// public static String filterNumber(String number) { -// if (number == null) return null; -// -// int length = number.length(); -// StringBuilder builder = new StringBuilder(length); -// -// for (int i = 0; i < length; i++) { -// char character = number.charAt(i); -// -// if (Character.isDigit(character) || character == '+') -// builder.append(character); -// } -// -// return builder.toString(); -// } } diff --git a/src/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java b/src/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java new file mode 100644 index 0000000000..37e8ceb105 --- /dev/null +++ b/src/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java @@ -0,0 +1,200 @@ +package org.thoughtcrime.securesms.phonenumbers; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; +import com.google.i18n.phonenumbers.ShortNumberInfo; + +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PhoneNumberFormatter { + + private static final String TAG = PhoneNumberFormatter.class.getSimpleName(); + + private static final Set SHORT_COUNTRIES = new HashSet() {{ + add("NU"); + add("TK"); + add("NC"); + add("AC"); + }}; + + private static final Pattern US_NO_AREACODE = Pattern.compile("^(\\d{7})$"); + private static final Pattern BR_NO_AREACODE = Pattern.compile("^(9?\\d{8})$"); + + private static final AtomicReference> cachedFormatter = new AtomicReference<>(); + + private final Optional localNumber; + private final String localCountryCode; + + private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); + + + public static @NonNull PhoneNumberFormatter get(Context context) { + String localNumber = TextSecurePreferences.getLocalNumber(context); + + if (!TextUtils.isEmpty(localNumber)) { + Pair cached = cachedFormatter.get(); + + if (cached != null && cached.first().equals(localNumber)) return cached.second(); + + PhoneNumberFormatter formatter = new PhoneNumberFormatter(localNumber); + cachedFormatter.set(new Pair<>(localNumber, formatter)); + + return formatter; + } else { + return new PhoneNumberFormatter(Util.getSimCountryIso(context).or("US"), true); + } + } + + PhoneNumberFormatter(@NonNull String localNumberString) { + try { + Phonenumber.PhoneNumber libNumber = phoneNumberUtil.parse(localNumberString, null); + int countryCode = libNumber.getCountryCode(); + + this.localNumber = Optional.of(new PhoneNumber(localNumberString, countryCode, parseAreaCode(localNumberString, countryCode))); + this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(libNumber); + } catch (NumberParseException e) { + throw new AssertionError(e); + } + } + + PhoneNumberFormatter(@NonNull String localCountryCode, boolean countryCode) { + this.localNumber = Optional.absent(); + this.localCountryCode = localCountryCode; + } + + public String format(@Nullable String number) { + if (number == null) return "Unknown"; + if (GroupUtil.isEncodedGroup(number)) return number; + if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); + + String bareNumber = number.replaceAll("[^0-9+]", ""); + + if (bareNumber.length() == 0) { + if (number.trim().length() == 0) return "Unknown"; + else return number.trim(); + } + + // libphonenumber doesn't seem to be correct for Germany and Finland + if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) { + return bareNumber; + } + + // libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes. + if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) { + return bareNumber; + } + + if (isShortCode(bareNumber, localCountryCode)) { + return bareNumber; + } + + String processedNumber = applyAreaCodeRules(localNumber, bareNumber); + + try { + Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(processedNumber, localCountryCode); + return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); + } catch (NumberParseException e) { + Log.w(TAG, e); + if (bareNumber.charAt(0) == '+') + return bareNumber; + + String localNumberImprecise = localNumber.isPresent() ? localNumber.get().getE164Number() : ""; + + if (localNumberImprecise.charAt(0) == '+') + localNumberImprecise = localNumberImprecise.substring(1); + + if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) + return "+" + number; + + int difference = localNumberImprecise.length() - bareNumber.length(); + + return "+" + localNumberImprecise.substring(0, difference) + bareNumber; + } + } + + private boolean isShortCode(@NonNull String bareNumber, String localCountryCode) { + try { + Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode); + return ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode); + } catch (NumberParseException e) { + return false; + } + } + + private @Nullable String parseAreaCode(@NonNull String e164Number, int countryCode) { + switch (countryCode) { + case 1: + return e164Number.substring(2, 5); + case 55: + return e164Number.substring(3, 5); + } + return null; + } + + + private @NonNull String applyAreaCodeRules(@NonNull Optional localNumber, @NonNull String testNumber) { + if (!localNumber.isPresent() || !localNumber.get().getAreaCode().isPresent()) { + return testNumber; + } + + Matcher matcher; + switch (localNumber.get().getCountryCode()) { + case 1: + matcher = US_NO_AREACODE.matcher(testNumber); + if (matcher.matches()) { + return localNumber.get().getAreaCode() + matcher.group(); + } + break; + + case 55: + matcher = BR_NO_AREACODE.matcher(testNumber); + if (matcher.matches()) { + return localNumber.get().getAreaCode() + matcher.group(); + } + } + return testNumber; + } + + private static class PhoneNumber { + private final String e164Number; + private final int countryCode; + private final Optional areaCode; + + PhoneNumber(String e164Number, int countryCode, @Nullable String areaCode) { + this.e164Number = e164Number; + this.countryCode = countryCode; + this.areaCode = Optional.fromNullable(areaCode); + } + + String getE164Number() { + return e164Number; + } + + int getCountryCode() { + return countryCode; + } + + Optional getAreaCode() { + return areaCode; + } + } +} diff --git a/src/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java b/src/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java index bca2027d75..9e66a6e637 100644 --- a/src/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java +++ b/src/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java @@ -9,16 +9,17 @@ import android.widget.TextView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.util.Util; -public class BlockedContactListItem extends RelativeLayout implements RecipientModifiedListener { +public class BlockedContactListItem extends RelativeLayout implements RecipientForeverObserver { private AvatarImageView contactPhotoImage; private TextView nameView; private GlideRequests glideRequests; - private Recipient recipient; + private LiveRecipient recipient; public BlockedContactListItem(Context context) { super(context); @@ -39,26 +40,33 @@ public class BlockedContactListItem extends RelativeLayout implements RecipientM this.nameView = findViewById(R.id.name); } - public void set(@NonNull GlideRequests glideRequests, @NonNull Recipient recipients) { - this.glideRequests = glideRequests; - this.recipient = recipients; + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (this.recipient != null) { + recipient.removeForeverObserver(this); + } + } - onModified(recipients); - recipients.addListener(this); + public void set(@NonNull GlideRequests glideRequests, @NonNull LiveRecipient recipient) { + this.glideRequests = glideRequests; + this.recipient = recipient; + + onRecipientChanged(recipient.get()); + + this.recipient.observeForever(this); } @Override - public void onModified(final Recipient recipients) { + public void onRecipientChanged(@NonNull Recipient recipient) { final AvatarImageView contactPhotoImage = this.contactPhotoImage; final TextView nameView = this.nameView; - Util.runOnMain(() -> { - contactPhotoImage.setAvatar(glideRequests, recipients, false); - nameView.setText(recipients.toShortString()); - }); + contactPhotoImage.setAvatar(glideRequests, recipient, false); + nameView.setText(recipient.toShortString()); } public Recipient getRecipient() { - return recipient; + return recipient.get(); } } diff --git a/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java b/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java index 93978bb68f..3aeb3b09a9 100644 --- a/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java +++ b/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java @@ -55,7 +55,7 @@ public class GroupShareProfileView extends FrameLayout { .setTitle(R.string.GroupShareProfileView_share_your_profile_name_and_photo_with_this_group) .setMessage(R.string.GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group) .setPositiveButton(R.string.GroupShareProfileView_make_visible, (dialog, which) -> { - DatabaseFactory.getRecipientDatabase(getContext()).setProfileSharing(recipient, true); + DatabaseFactory.getRecipientDatabase(getContext()).setProfileSharing(recipient.getId(), true); }) .setNegativeButton(android.R.string.cancel, null) .show(); diff --git a/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java index 8a9e76d43a..e0c92fe160 100644 --- a/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java +++ b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java @@ -46,7 +46,7 @@ public class UnknownSenderView extends FrameLayout { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient.getId(), true); if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); return null; } @@ -72,7 +72,7 @@ public class UnknownSenderView extends FrameLayout { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true); if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); return null; } diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java new file mode 100644 index 0000000000..113f90b7f9 --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -0,0 +1,193 @@ +package org.thoughtcrime.securesms.recipients; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import com.annimon.stream.Stream; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public final class LiveRecipient { + + private final Context context; + private final MutableLiveData liveData; + private final Set observers; + private final Observer foreverObserver; + private final Recipient defaultRecipient; + private final RecipientDatabase recipientDatabase; + private final GroupDatabase groupDatabase; + private final String unnamedGroupName; + + LiveRecipient(@NonNull Context context, @NonNull MutableLiveData liveData, @NonNull Recipient defaultRecipient) { + this.context = context.getApplicationContext(); + this.liveData = liveData; + this.defaultRecipient = defaultRecipient; + this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + this.groupDatabase = DatabaseFactory.getGroupDatabase(context); + this.unnamedGroupName = context.getString(R.string.RecipientProvider_unnamed_group); + this.observers = new CopyOnWriteArraySet<>(); + this.foreverObserver = recipient -> { + for (RecipientForeverObserver o : observers) { + o.onRecipientChanged(recipient); + } + }; + } + + public @NonNull RecipientId getId() { + return defaultRecipient.getId(); + } + + /** + * @return A recipient that may or may not be fully-resolved. + */ + public @NonNull Recipient get() { + Recipient live = liveData.getValue(); + + if (live == null) { + return defaultRecipient; + } else { + return live; + } + } + + /** + * Watch the recipient for changes. The callback will only be invoked if the provided lifecycle is + * in a valid state. No need to remove the observer. If you do wish to remove the observer (if, + * for instance, you wish to remove the listener before the end of the owner's lifecycle), you can + * use {@link #removeObservers(LifecycleOwner)}. + */ + public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) { + Util.postToMain(() -> liveData.observe(owner, observer)); + } + + /** + * Removes all observers of this data registered for the given LifecycleOwner. + */ + public void removeObservers(@NonNull LifecycleOwner owner) { + Util.runOnMain(() -> liveData.removeObservers(owner)); + } + + /** + * Watch the recipient for changes. The callback could be invoked at any time. You MUST call + * {@link #removeForeverObserver(RecipientForeverObserver)} when finished. You should use + * {@link #observe(LifecycleOwner, Observer)} if possible, as it is lifecycle-safe. + */ + public void observeForever(@NonNull RecipientForeverObserver observer) { + Util.postToMain(() -> { + observers.add(observer); + + if (observers.size() == 1) { + liveData.observeForever(foreverObserver); + } + }); + } + + /** + * Unsubscribes the provided {@link RecipientForeverObserver} from future changes. + */ + public void removeForeverObserver(@NonNull RecipientForeverObserver observer) { + Util.postToMain(() -> { + observers.remove(observer); + + if (observers.isEmpty()) { + liveData.removeObserver(foreverObserver); + } + }); + } + + /** + * @return A fully-resolved version of the recipient. May require reading from disk. + */ + @WorkerThread + public synchronized @NonNull Recipient resolve() { + Recipient recipient = get(); + + if (recipient.isResolving()) { + recipient = fetchRecipientFromDisk(defaultRecipient.getId()); + liveData.postValue(recipient); + Stream.of(recipient.getParticipants()).forEach(Recipient::resolve); + } + + return recipient; + } + + /** + * Forces a reload of the underlying recipient. + */ + @WorkerThread + public synchronized void refresh() { + Recipient recipient = fetchRecipientFromDisk(defaultRecipient.getId()); + liveData.postValue(recipient); + Stream.of(recipient.getParticipants()).map(Recipient::live).forEach(LiveRecipient::refresh); + } + + + private @NonNull Recipient fetchRecipientFromDisk(RecipientId id) { + RecipientSettings settings = recipientDatabase.getRecipientSettings(id); + RecipientDetails details = settings.getAddress().isGroup() ? getGroupRecipientDetails(settings) + : getIndividualRecipientDetails(settings); + + return new Recipient(id, details); + } + + private @NonNull RecipientDetails getIndividualRecipientDetails(RecipientSettings settings) { + boolean systemContact = !TextUtils.isEmpty(settings.getSystemDisplayName()); + boolean isLocalNumber = settings.getAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context)); + return new RecipientDetails(null, Optional.absent(), systemContact, isLocalNumber, settings, null); + } + + @WorkerThread + private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientSettings settings) { + Optional groupRecord = groupDatabase.getGroup(settings.getId()); + + if (groupRecord.isPresent()) { + String title = groupRecord.get().getTitle(); + List members = Stream.of(groupRecord.get().getMembers()).map(Recipient::resolved).toList(); + Optional avatarId = Optional.absent(); + + if (!settings.getAddress().isMmsGroup() && title == null) { + title = unnamedGroupName; + } + + if (groupRecord.get().getAvatar() != null && groupRecord.get().getAvatar().length > 0) { + avatarId = Optional.of(groupRecord.get().getAvatarId()); + } + + return new RecipientDetails(title, avatarId, false, false, settings, members); + } + + return new RecipientDetails(unnamedGroupName, Optional.absent(), false, false, settings, null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LiveRecipient that = (LiveRecipient) o; + return defaultRecipient.equals(that.defaultRecipient); + } + + @Override + public int hashCode() { + return Objects.hash(defaultRecipient); + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java new file mode 100644 index 0000000000..07c09c1211 --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -0,0 +1,62 @@ +package org.thoughtcrime.securesms.recipients; + +import android.annotation.SuppressLint; +import android.content.Context; + +import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; +import androidx.lifecycle.MutableLiveData; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.util.LRUCache; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; + +import java.util.Map; + +public final class LiveRecipientCache { + + + private final Context context; + private final RecipientDatabase recipientDatabase; + private final Map recipients; + private final LiveRecipient unknown; + + private RecipientId localRecipientId; + + @SuppressLint("UseSparseArrays") + public LiveRecipientCache(@NonNull Context context) { + this.context = context.getApplicationContext(); + this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + this.recipients = new LRUCache<>(1000); + this.unknown = new LiveRecipient(context, new MutableLiveData<>(), Recipient.UNKNOWN); + } + + @AnyThread + synchronized @NonNull LiveRecipient getLive(@NonNull RecipientId id) { + if (id.isUnknown()) return unknown; + + LiveRecipient live = recipients.get(id); + + if (live == null) { + final LiveRecipient newLive = new LiveRecipient(context, new MutableLiveData<>(), new Recipient(id)); + + recipients.put(id, newLive); + SignalExecutors.BOUNDED.execute(newLive::resolve); + + live = newLive; + } + + return live; + } + + synchronized @NonNull Recipient getSelf() { + if (localRecipientId == null) { + localRecipientId = recipientDatabase.getOrInsertFromE164(TextSecurePreferences.getLocalNumber(context)); + } + + return getLive(localRecipientId).resolve(); + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index ec144f6302..bb6a767f03 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -1,30 +1,14 @@ -/* - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 - 2017 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ package org.thoughtcrime.securesms.recipients; import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.TextUtils; -import com.annimon.stream.function.Consumer; +import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.color.MaterialColor; @@ -38,256 +22,179 @@ import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.TransparentContactPhoto; import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; -import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; -import org.thoughtcrime.securesms.util.FutureTaskListener; -import org.thoughtcrime.securesms.util.ListenableFutureTask; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.ExecutionException; +import java.util.Objects; -public class Recipient implements RecipientModifiedListener { +public class Recipient { - private static final String TAG = Recipient.class.getSimpleName(); - private static final RecipientProvider provider = new RecipientProvider(); + public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN); - private final Set listeners = Collections.newSetFromMap(new WeakHashMap()); + private final RecipientId id; + private final boolean resolving; + private final Address address; + private final List participants; + private final Optional groupAvatarId; + private final boolean localNumber; + private final boolean blocked; + private final long muteUntil; + private final VibrateState messageVibrate; + private final VibrateState callVibrate; + private final Uri messageRingtone; + private final Uri callRingtone; + private final MaterialColor color; + private final boolean seenInviteReminder; + private final Optional defaultSubscriptionId; + private final int expireMessages; + private final RegisteredState registered; + private final byte[] profileKey; + private final String name; + private final Uri systemContactPhoto; + private final String customLabel; + private final Uri contactUri; + private final String profileName; + private final String profileAvatar; + private final boolean profileSharing; + private final String notificationChannel; + private final UnidentifiedAccessMode unidentifiedAccessMode; + private final boolean forceSmsSelection; - private final @NonNull Address address; - private final @NonNull List participants = new LinkedList<>(); - private @Nullable String name; - private @Nullable String customLabel; - private boolean resolving; - private boolean isLocalNumber; - - private @Nullable Uri systemContactPhoto; - private @Nullable Long groupAvatarId; - private Uri contactUri; - private @Nullable Uri messageRingtone = null; - private @Nullable Uri callRingtone = null; - private long mutedUntil = 0; - private boolean blocked = false; - private VibrateState messageVibrate = VibrateState.DEFAULT; - private VibrateState callVibrate = VibrateState.DEFAULT; - private int expireMessages = 0; - private Optional defaultSubscriptionId = Optional.absent(); - private @NonNull RegisteredState registered = RegisteredState.UNKNOWN; - - private @Nullable MaterialColor color; - private boolean seenInviteReminder; - private @Nullable byte[] profileKey; - private @Nullable String profileName; - private @Nullable String profileAvatar; - private boolean profileSharing; - private String notificationChannel; - private boolean forceSmsSelection; - - private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; - - @SuppressWarnings("ConstantConditions") - public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { - if (address == null) throw new AssertionError(address); - return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous); + /** + * Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be + * populated with data. However, you can observe the value that's returned to be notified when the + * {@link Recipient} changes. + */ + @AnyThread + public static @NonNull LiveRecipient live(@NonNull RecipientId id) { + return ApplicationDependencies.getRecipientCache().getLive(id); } - @SuppressWarnings("ConstantConditions") - public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { - if (address == null) throw new AssertionError(address); - return provider.getRecipient(context, address, settings, groupRecord, asynchronous); + /** + * Returns a fully-populated {@link Recipient}. May hit the disk, and therefore should be + * called on a background thread. + */ + @WorkerThread + public static @NonNull Recipient resolved(@NonNull RecipientId id) { + return live(id).resolve(); } - public static void applyCached(@NonNull Address address, Consumer consumer) { - Optional recipient = provider.getCached(address); - if (recipient.isPresent()) consumer.accept(recipient.get()); - } + /** + * Returns a fully-populated {@link Recipient} based off of a string identifier, creating one in + * the database if necessary. The identifier may be a phone number, email, or serialized groupId. + */ + @WorkerThread + public static @NonNull Recipient external(@NonNull Context context, @NonNull String address) { + RecipientDatabase db = DatabaseFactory.getRecipientDatabase(context); + RecipientId id = null; - Recipient(@NonNull Address address, - @Nullable Recipient stale, - @NonNull Optional details, - @NonNull ListenableFutureTask future) - { - this.address = address; - this.color = null; - this.resolving = true; - - if (stale != null) { - this.name = stale.name; - this.contactUri = stale.contactUri; - this.systemContactPhoto = stale.systemContactPhoto; - this.groupAvatarId = stale.groupAvatarId; - this.isLocalNumber = stale.isLocalNumber; - this.color = stale.color; - this.customLabel = stale.customLabel; - this.messageRingtone = stale.messageRingtone; - this.callRingtone = stale.callRingtone; - this.mutedUntil = stale.mutedUntil; - this.blocked = stale.blocked; - this.messageVibrate = stale.messageVibrate; - this.callVibrate = stale.callVibrate; - this.expireMessages = stale.expireMessages; - this.seenInviteReminder = stale.seenInviteReminder; - this.defaultSubscriptionId = stale.defaultSubscriptionId; - this.registered = stale.registered; - this.notificationChannel = stale.notificationChannel; - this.profileKey = stale.profileKey; - this.profileName = stale.profileName; - this.profileAvatar = stale.profileAvatar; - this.profileSharing = stale.profileSharing; - this.unidentifiedAccessMode = stale.unidentifiedAccessMode; - this.forceSmsSelection = stale.forceSmsSelection; - - this.participants.clear(); - this.participants.addAll(stale.participants); + if (GroupUtil.isEncodedGroup(address)) { + id = db.getOrInsertFromGroupId(address); + } else if (NumberUtil.isValidEmail(address)) { + id = db.getOrInsertFromEmail(address); + } else { + String e164 = PhoneNumberFormatter.get(context).format(address); + id = db.getOrInsertFromE164(e164); } - if (details.isPresent()) { - this.name = details.get().name; - this.systemContactPhoto = details.get().systemContactPhoto; - this.groupAvatarId = details.get().groupAvatarId; - this.isLocalNumber = details.get().isLocalNumber; - this.color = details.get().color; - this.messageRingtone = details.get().messageRingtone; - this.callRingtone = details.get().callRingtone; - this.mutedUntil = details.get().mutedUntil; - this.blocked = details.get().blocked; - this.messageVibrate = details.get().messageVibrateState; - this.callVibrate = details.get().callVibrateState; - this.expireMessages = details.get().expireMessages; - this.seenInviteReminder = details.get().seenInviteReminder; - this.defaultSubscriptionId = details.get().defaultSubscriptionId; - this.registered = details.get().registered; - this.notificationChannel = details.get().notificationChannel; - this.profileKey = details.get().profileKey; - this.profileName = details.get().profileName; - this.profileAvatar = details.get().profileAvatar; - this.profileSharing = details.get().profileSharing; - this.unidentifiedAccessMode = details.get().unidentifiedAccessMode; - this.forceSmsSelection = details.get().forceSmsSelection; - - this.participants.clear(); - this.participants.addAll(details.get().participants); - } - - future.addListener(new FutureTaskListener() { - @Override - public void onSuccess(RecipientDetails result) { - if (result != null) { - synchronized (Recipient.this) { - Recipient.this.name = result.name; - Recipient.this.contactUri = result.contactUri; - Recipient.this.systemContactPhoto = result.systemContactPhoto; - Recipient.this.groupAvatarId = result.groupAvatarId; - Recipient.this.isLocalNumber = result.isLocalNumber; - Recipient.this.color = result.color; - Recipient.this.customLabel = result.customLabel; - Recipient.this.messageRingtone = result.messageRingtone; - Recipient.this.callRingtone = result.callRingtone; - Recipient.this.mutedUntil = result.mutedUntil; - Recipient.this.blocked = result.blocked; - Recipient.this.messageVibrate = result.messageVibrateState; - Recipient.this.callVibrate = result.callVibrateState; - Recipient.this.expireMessages = result.expireMessages; - Recipient.this.seenInviteReminder = result.seenInviteReminder; - Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId; - Recipient.this.registered = result.registered; - Recipient.this.notificationChannel = result.notificationChannel; - Recipient.this.profileKey = result.profileKey; - Recipient.this.profileName = result.profileName; - Recipient.this.profileAvatar = result.profileAvatar; - Recipient.this.profileSharing = result.profileSharing; - Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode; - Recipient.this.forceSmsSelection = result.forceSmsSelection; - - Recipient.this.participants.clear(); - Recipient.this.participants.addAll(result.participants); - Recipient.this.resolving = false; - - if (!listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.addListener(Recipient.this); - } - - Recipient.this.notifyAll(); - } - - notifyListeners(); - } - } - - @Override - public void onFailure(ExecutionException error) { - Log.w(TAG, error); - } - }); + return Recipient.resolved(id); } - Recipient(@NonNull Address address, @NonNull RecipientDetails details) { - this.address = address; - this.contactUri = details.contactUri; - this.name = details.name; - this.systemContactPhoto = details.systemContactPhoto; + public static @NonNull Recipient self() { + return ApplicationDependencies.getRecipientCache().getSelf(); + } + + Recipient(@NonNull RecipientId id) { + this.id = id; + this.resolving = true; + this.address = null; + this.participants = Collections.emptyList(); + this.groupAvatarId = Optional.absent(); + this.localNumber = false; + this.blocked = false; + this.muteUntil = 0; + this.messageVibrate = VibrateState.DEFAULT; + this.callVibrate = VibrateState.DEFAULT; + this.messageRingtone = null; + this.callRingtone = null; + this.color = null; + this.seenInviteReminder = true; + this.defaultSubscriptionId = Optional.absent(); + this.expireMessages = 0; + this.registered = RegisteredState.UNKNOWN; + this.profileKey = null; + this.name = null; + this.systemContactPhoto = null; + this.customLabel = null; + this.contactUri = null; + this.profileName = null; + this.profileAvatar = null; + this.profileSharing = false; + this.notificationChannel = null; + this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; + this.forceSmsSelection = false; + } + + Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details) { + this.id = id; + this.resolving = false; + this.address = details.address; + this.participants = details.participants; this.groupAvatarId = details.groupAvatarId; - this.isLocalNumber = details.isLocalNumber; - this.color = details.color; - this.customLabel = details.customLabel; - this.messageRingtone = details.messageRingtone; - this.callRingtone = details.callRingtone; - this.mutedUntil = details.mutedUntil; + this.localNumber = details.isLocalNumber; this.blocked = details.blocked; + this.muteUntil = details.mutedUntil; this.messageVibrate = details.messageVibrateState; this.callVibrate = details.callVibrateState; - this.expireMessages = details.expireMessages; + this.messageRingtone = details.messageRingtone; + this.callRingtone = details.callRingtone; + this.color = details.color; this.seenInviteReminder = details.seenInviteReminder; this.defaultSubscriptionId = details.defaultSubscriptionId; + this.expireMessages = details.expireMessages; this.registered = details.registered; - this.notificationChannel = details.notificationChannel; this.profileKey = details.profileKey; + this.name = details.name; + this.systemContactPhoto = details.systemContactPhoto; + this.customLabel = details.customLabel; + this.contactUri = details.contactUri; this.profileName = details.profileName; this.profileAvatar = details.profileAvatar; this.profileSharing = details.profileSharing; + this.notificationChannel = details.notificationChannel; this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.forceSmsSelection = details.forceSmsSelection; + } - this.participants.addAll(details.participants); - this.resolving = false; + public @NonNull RecipientId getId() { + return id; } public boolean isLocalNumber() { - return isLocalNumber; + return localNumber; } - public synchronized @Nullable Uri getContactUri() { - return this.contactUri; + public @Nullable Uri getContactUri() { + return contactUri; } - public void setContactUri(@Nullable Uri contactUri) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(contactUri, this.contactUri)) { - this.contactUri = contactUri; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized @Nullable String getName() { - if (this.name == null && isMmsGroupRecipient()) { + public @Nullable String getName() { + if (this.name == null && isMmsGroup()) { List names = new LinkedList<>(); for (Recipient recipient : participants) { @@ -300,194 +207,84 @@ public class Recipient implements RecipientModifiedListener { return this.name; } - public void setName(@Nullable String name) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.name, name)) { - this.name = name; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized @NonNull MaterialColor getColor() { - if (isGroupRecipient()) return MaterialColor.GROUP; + public @NonNull MaterialColor getColor() { + if (isGroup()) return MaterialColor.GROUP; else if (color != null) return color; else if (name != null) return ContactColors.generateFor(name); else return ContactColors.UNKNOWN_COLOR; } - public void setColor(@NonNull MaterialColor color) { - synchronized (this) { - this.color = color; + public @NonNull Address requireAddress() { + if (resolving) { + return resolve().address; + } else { + return address; } - - notifyListeners(); } - public @NonNull Address getAddress() { - return address; - } - - public synchronized @Nullable String getCustomLabel() { + public @Nullable String getCustomLabel() { return customLabel; } - public void setCustomLabel(@Nullable String customLabel) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(customLabel, this.customLabel)) { - this.customLabel = customLabel; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized Optional getDefaultSubscriptionId() { + public Optional getDefaultSubscriptionId() { return defaultSubscriptionId; } - public void setDefaultSubscriptionId(Optional defaultSubscriptionId) { - synchronized (this) { - this.defaultSubscriptionId = defaultSubscriptionId; - } - - notifyListeners(); - } - - public synchronized @Nullable String getProfileName() { + public @Nullable String getProfileName() { return profileName; } - public void setProfileName(@Nullable String profileName) { - synchronized (this) { - this.profileName = profileName; - } - - notifyListeners(); - } - - public synchronized @Nullable String getProfileAvatar() { + public @Nullable String getProfileAvatar() { return profileAvatar; } - public void setProfileAvatar(@Nullable String profileAvatar) { - synchronized (this) { - this.profileAvatar = profileAvatar; - } - - notifyListeners(); - } - - public synchronized boolean isProfileSharing() { + public boolean isProfileSharing() { return profileSharing; } - public void setProfileSharing(boolean value) { - synchronized (this) { - this.profileSharing = value; - } - - notifyListeners(); + public boolean isGroup() { + return address != null && address.isGroup(); } - public boolean isGroupRecipient() { - return address.isGroup(); + public boolean isMmsGroup() { + return address != null && address.isMmsGroup(); } - public boolean isMmsGroupRecipient() { - return address.isMmsGroup(); + public boolean isPushGroup() { + return address != null && address.isGroup() && !address.isMmsGroup(); } - public boolean isPushGroupRecipient() { - return address.isGroup() && !address.isMmsGroup(); + public @NonNull List getParticipants() { + return new ArrayList<>(participants); } - public @NonNull synchronized List getParticipants() { - return new LinkedList<>(participants); + public @NonNull String toShortString() { + return getName() == null ? address == null ? "" + : address.serialize() + : getName(); } - public void setParticipants(@NonNull List participants) { - synchronized (this) { - this.participants.clear(); - this.participants.addAll(participants); - } - - notifyListeners(); - } - - public synchronized void addListener(RecipientModifiedListener listener) { - if (listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.addListener(this); - } - listeners.add(listener); - } - - public synchronized void removeListener(RecipientModifiedListener listener) { - listeners.remove(listener); - - if (listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.removeListener(this); - } - } - - public synchronized String toShortString() { - return (getName() == null ? address.serialize() : getName()); - } - - public synchronized @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { + public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { return getFallbackContactPhoto().asDrawable(context, getColor().toAvatarColor(context), inverted); } - public synchronized @NonNull FallbackContactPhoto getFallbackContactPhoto() { - if (isLocalNumber) return new ResourceContactPhoto(R.drawable.ic_note_to_self); + public @NonNull FallbackContactPhoto getFallbackContactPhoto() { + if (localNumber) return new ResourceContactPhoto(R.drawable.ic_note_to_self); if (isResolving()) return new TransparentContactPhoto(); - else if (isGroupRecipient()) return new ResourceContactPhoto(R.drawable.ic_group_white_24dp, R.drawable.ic_group_large); + else if (isGroup()) return new ResourceContactPhoto(R.drawable.ic_group_white_24dp, R.drawable.ic_group_large); else if (!TextUtils.isEmpty(name)) return new GeneratedContactPhoto(name, R.drawable.ic_profile_default); else return new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large); } - public synchronized @Nullable ContactPhoto getContactPhoto() { - if (isLocalNumber) return null; - else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId); - else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); - else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); - else return null; + public @Nullable ContactPhoto getContactPhoto() { + if (localNumber) return null; + else if (isGroup() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(address, groupAvatarId.get()); + else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); + else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); + else return null; } - public void setSystemContactPhoto(@Nullable Uri systemContactPhoto) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(systemContactPhoto, this.systemContactPhoto)) { - this.systemContactPhoto = systemContactPhoto; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public void setGroupAvatarId(@Nullable Long groupAvatarId) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.groupAvatarId, groupAvatarId)) { - this.groupAvatarId = groupAvatarId; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized @Nullable Uri getMessageRingtone() { + public @Nullable Uri getMessageRingtone() { if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) { return null; } @@ -495,15 +292,7 @@ public class Recipient implements RecipientModifiedListener { return messageRingtone; } - public void setMessageRingtone(@Nullable Uri ringtone) { - synchronized (this) { - this.messageRingtone = ringtone; - } - - notifyListeners(); - } - - public synchronized @Nullable Uri getCallRingtone() { + public @Nullable Uri getCallRingtone() { if (callRingtone != null && callRingtone.getScheme() != null && callRingtone.getScheme().startsWith("file")) { return null; } @@ -511,203 +300,83 @@ public class Recipient implements RecipientModifiedListener { return callRingtone; } - public void setCallRingtone(@Nullable Uri ringtone) { - synchronized (this) { - this.callRingtone = ringtone; - } - - notifyListeners(); + public boolean isMuted() { + return System.currentTimeMillis() <= muteUntil; } - public synchronized boolean isMuted() { - return System.currentTimeMillis() <= mutedUntil; - } - - public void setMuted(long mutedUntil) { - synchronized (this) { - this.mutedUntil = mutedUntil; - } - - notifyListeners(); - } - - public synchronized boolean isBlocked() { + public boolean isBlocked() { return blocked; } - public void setBlocked(boolean blocked) { - synchronized (this) { - this.blocked = blocked; - } - - notifyListeners(); - } - - public synchronized VibrateState getMessageVibrate() { + public @NonNull VibrateState getMessageVibrate() { return messageVibrate; } - public void setMessageVibrate(VibrateState vibrate) { - synchronized (this) { - this.messageVibrate = vibrate; - } - - notifyListeners(); - } - - public synchronized VibrateState getCallVibrate() { + public @NonNull VibrateState getCallVibrate() { return callVibrate; } - public void setCallVibrate(VibrateState vibrate) { - synchronized (this) { - this.callVibrate = vibrate; - } - - notifyListeners(); - } - - public synchronized int getExpireMessages() { + public int getExpireMessages() { return expireMessages; } - public void setExpireMessages(int expireMessages) { - synchronized (this) { - this.expireMessages = expireMessages; - } - - notifyListeners(); - } - - public synchronized boolean hasSeenInviteReminder() { + public boolean hasSeenInviteReminder() { return seenInviteReminder; } - public void setHasSeenInviteReminder(boolean value) { - synchronized (this) { - this.seenInviteReminder = value; - } - - notifyListeners(); - } - - public synchronized RegisteredState getRegistered() { - if (isPushGroupRecipient()) return RegisteredState.REGISTERED; - else if (isMmsGroupRecipient()) return RegisteredState.NOT_REGISTERED; + public @NonNull RegisteredState getRegistered() { + if (isPushGroup()) return RegisteredState.REGISTERED; + else if (isMmsGroup()) return RegisteredState.NOT_REGISTERED; return registered; } - public void setRegistered(@NonNull RegisteredState value) { - boolean notify = false; - - synchronized (this) { - if (this.registered != value) { - this.registered = value; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized @Nullable String getNotificationChannel() { + public @Nullable String getNotificationChannel() { return !NotificationChannels.supported() ? null : notificationChannel; } - public void setNotificationChannel(@Nullable String value) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.notificationChannel, value)) { - this.notificationChannel = value; - notify = true; - } - } - - if (notify) notifyListeners(); - } - public boolean isForceSmsSelection() { return forceSmsSelection; } - public void setForceSmsSelection(boolean value) { - synchronized (this) { - this.forceSmsSelection = value; - } - - notifyListeners(); - } - - public synchronized @Nullable byte[] getProfileKey() { + public @Nullable byte[] getProfileKey() { return profileKey; } - public void setProfileKey(@Nullable byte[] profileKey) { - synchronized (this) { - this.profileKey = profileKey; - } - - notifyListeners(); - } - - public @NonNull synchronized UnidentifiedAccessMode getUnidentifiedAccessMode() { + public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { return unidentifiedAccessMode; } - public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) { - synchronized (this) { - this.unidentifiedAccessMode = unidentifiedAccessMode; - } - - notifyListeners(); - } - - public synchronized boolean isSystemContact() { + public boolean isSystemContact() { return contactUri != null; } - public synchronized Recipient resolve() { - while (resolving) Util.wait(this, 0); - return this; + public Recipient resolve() { + if (resolving) { + return live().resolve(); + } else { + return this; + } } + public boolean isResolving() { + return resolving; + } + + public @NonNull LiveRecipient live() { + return ApplicationDependencies.getRecipientCache().getLive(id); + } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof Recipient)) return false; - - Recipient that = (Recipient) o; - - return this.address.equals(that.address); + if (o == null || getClass() != o.getClass()) return false; + Recipient recipient = (Recipient) o; + return id.equals(recipient.id); } @Override public int hashCode() { - return this.address.hashCode(); + return Objects.hash(id); } - - private void notifyListeners() { - Set localListeners; - - synchronized (this) { - localListeners = new HashSet<>(listeners); - } - - for (RecipientModifiedListener listener : localListeners) - listener.onModified(this); - } - - @Override - public void onModified(Recipient recipient) { - notifyListeners(); - } - - public synchronized boolean isResolving() { - return resolving; - } - - } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java new file mode 100644 index 0000000000..3b83fd69ec --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -0,0 +1,87 @@ +package org.thoughtcrime.securesms.recipients; + +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.color.MaterialColor; +import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; +import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; +import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; +import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.util.LinkedList; +import java.util.List; + +public class RecipientDetails { + + final Address address; + final String name; + final String customLabel; + final Uri systemContactPhoto; + final Uri contactUri; + final Optional groupAvatarId; + final MaterialColor color; + final Uri messageRingtone; + final Uri callRingtone; + final long mutedUntil; + final VibrateState messageVibrateState; + final VibrateState callVibrateState; + final boolean blocked; + final int expireMessages; + final List participants; + final String profileName; + final boolean seenInviteReminder; + final Optional defaultSubscriptionId; + final RegisteredState registered; + final byte[] profileKey; + final String profileAvatar; + final boolean profileSharing; + final boolean systemContact; + final boolean isLocalNumber; + final String notificationChannel; + final UnidentifiedAccessMode unidentifiedAccessMode; + final boolean forceSmsSelection; + + RecipientDetails(@Nullable String name, + @NonNull Optional groupAvatarId, + boolean systemContact, + boolean isLocalNumber, + @NonNull RecipientSettings settings, + @Nullable List participants) + { + this.groupAvatarId = groupAvatarId; + this.systemContactPhoto = Util.uri(settings.getSystemContactPhotoUri()); + this.customLabel = settings.getSystemPhoneLabel(); + this.contactUri = Util.uri(settings.getSystemContactUri()); + this.address = settings.getAddress(); + this.color = settings.getColor(); + this.messageRingtone = settings.getMessageRingtone(); + this.callRingtone = settings.getCallRingtone(); + this.mutedUntil = settings.getMuteUntil(); + this.messageVibrateState = settings.getMessageVibrateState(); + this.callVibrateState = settings.getCallVibrateState(); + this.blocked = settings.isBlocked(); + this.expireMessages = settings.getExpireMessages(); + this.participants = participants == null ? new LinkedList<>() : participants; + this.profileName = settings.getProfileName(); + this.seenInviteReminder = settings.hasSeenInviteReminder(); + this.defaultSubscriptionId = settings.getDefaultSubscriptionId(); + this.registered = settings.getRegistered(); + this.profileKey = settings.getProfileKey(); + this.profileAvatar = settings.getProfileAvatar(); + this.profileSharing = settings.isProfileSharing(); + this.systemContact = systemContact; + this.isLocalNumber = isLocalNumber; + this.notificationChannel = settings.getNotificationChannel(); + this.unidentifiedAccessMode = settings.getUnidentifiedAccessMode(); + this.forceSmsSelection = settings.isForceSmsSelection(); + + if (name == null) this.name = settings.getSystemDisplayName(); + else this.name = name; + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java index d8caceedf1..d9b18e7c2b 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java @@ -24,7 +24,7 @@ public final class RecipientExporter { Intent intent = new Intent(ACTION_INSERT_OR_EDIT); intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); addNameToIntent(intent, recipient.getProfileName()); - addAddressToIntent(intent, recipient.getAddress()); + addAddressToIntent(intent, recipient.requireAddress()); return intent; } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientForeverObserver.java b/src/org/thoughtcrime/securesms/recipients/RecipientForeverObserver.java new file mode 100644 index 0000000000..d43984dc55 --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/RecipientForeverObserver.java @@ -0,0 +1,9 @@ +package org.thoughtcrime.securesms.recipients; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; + +public interface RecipientForeverObserver { + @MainThread + void onRecipientChanged(@NonNull Recipient recipient); +} diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientId.java b/src/org/thoughtcrime/securesms/recipients/RecipientId.java new file mode 100644 index 0000000000..1babb4b684 --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/RecipientId.java @@ -0,0 +1,115 @@ +package org.thoughtcrime.securesms.recipients; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.annimon.stream.Stream; + +import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.Util; + +import java.util.ArrayList; +import java.util.List; + +public class RecipientId implements Parcelable, Comparable { + + private static final long UNKNOWN_ID = -1; + private static final char DELIMITER = ','; + + public static final RecipientId UNKNOWN = RecipientId.from(UNKNOWN_ID); + + private final long id; + + public static RecipientId from(long id) { + return new RecipientId(id); + } + + public static RecipientId from(@NonNull String id) { + return RecipientId.from(Long.parseLong(id)); + } + + private RecipientId(long id) { + this.id = id; + } + + private RecipientId(Parcel in) { + id = in.readLong(); + } + + public static @NonNull String toSerializedList(@NonNull List ids) { + return Util.join(Stream.of(ids).map(RecipientId::serialize).toList(), String.valueOf(DELIMITER)); + } + + public static List fromSerializedList(@NonNull String serialized) { + String[] stringIds = DelimiterUtil.split(serialized, DELIMITER); + List out = new ArrayList<>(stringIds.length); + + for (String stringId : stringIds) { + RecipientId id = RecipientId.from(Long.parseLong(stringId)); + out.add(id); + } + + return out; + } + + public boolean isUnknown() { + return id == UNKNOWN_ID; + } + + public @NonNull String serialize() { + return String.valueOf(id); + } + + public @NonNull String toQueueKey() { + return "RecipientId::" + id; + } + + @Override + public @NonNull String toString() { + return "RecipientId::" + id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RecipientId that = (RecipientId) o; + + return id == that.id; + } + + @Override + public int hashCode() { + return (int) (id ^ (id >>> 32)); + } + + @Override + public int compareTo(RecipientId o) { + return Long.compare(this.id, o.id); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + } + + public static final Creator CREATOR = new Creator() { + @Override + public RecipientId createFromParcel(Parcel in) { + return new RecipientId(in); + } + + @Override + public RecipientId[] newArray(int size) { + return new RecipientId[size]; + } + }; +} diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientModifiedListener.java b/src/org/thoughtcrime/securesms/recipients/RecipientModifiedListener.java index d6e0955c3f..a59a4b5a9f 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientModifiedListener.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientModifiedListener.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.recipients; +import androidx.annotation.NonNull; + public interface RecipientModifiedListener { - public void onModified(Recipient recipient); + public void onModified(@NonNull Recipient recipient); } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java deleted file mode 100644 index 1140d5d9c3..0000000000 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.recipients; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.color.MaterialColor; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; -import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; -import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; -import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; -import org.thoughtcrime.securesms.util.ListenableFutureTask; -import org.thoughtcrime.securesms.util.SoftHashMap; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.util.guava.Optional; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; - -class RecipientProvider { - - @SuppressWarnings("unused") - private static final String TAG = RecipientProvider.class.getSimpleName(); - - private static final RecipientCache recipientCache = new RecipientCache(); - private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor(); - - private static final Map STATIC_DETAILS = new HashMap() {{ - put("262966", new RecipientDetails("Amazon", null, false, false, null, null)); - }}; - - @NonNull Recipient getRecipient(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { - Recipient cachedRecipient = recipientCache.get(address); - - if (cachedRecipient != null && (asynchronous || !cachedRecipient.isResolving()) && ((!groupRecord.isPresent() && !settings.isPresent()) || !cachedRecipient.isResolving() || cachedRecipient.getName() != null)) { - return cachedRecipient; - } - - Optional prefetchedRecipientDetails = createPrefetchedRecipientDetails(context, address, settings, groupRecord); - - if (asynchronous) { - cachedRecipient = new Recipient(address, cachedRecipient, prefetchedRecipientDetails, getRecipientDetailsAsync(context, address, settings, groupRecord)); - } else { - cachedRecipient = new Recipient(address, getRecipientDetailsSync(context, address, settings, groupRecord, false)); - } - - recipientCache.set(address, cachedRecipient); - return cachedRecipient; - } - - @NonNull Optional getCached(@NonNull Address address) { - return Optional.fromNullable(recipientCache.get(address)); - } - - private @NonNull Optional createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address, - @NonNull Optional settings, - @NonNull Optional groupRecord) - { - if (address.isGroup() && settings.isPresent() && groupRecord.isPresent()) { - return Optional.of(getGroupRecipientDetails(context, address, groupRecord, settings, true)); - } else if (!address.isGroup() && settings.isPresent()) { - boolean isLocalNumber = address.serialize().equals(TextSecurePreferences.getLocalNumber(context)); - return Optional.of(new RecipientDetails(null, null, !TextUtils.isEmpty(settings.get().getSystemDisplayName()), isLocalNumber, settings.get(), null)); - } - - return Optional.absent(); - } - - private @NonNull ListenableFutureTask getRecipientDetailsAsync(final Context context, final @NonNull Address address, final @NonNull Optional settings, final @NonNull Optional groupRecord) - { - Callable task = () -> getRecipientDetailsSync(context, address, settings, groupRecord, true); - - ListenableFutureTask future = new ListenableFutureTask<>(task); - asyncRecipientResolver.submit(future); - return future; - } - - private @NonNull RecipientDetails getRecipientDetailsSync(Context context, @NonNull Address address, Optional settings, Optional groupRecord, boolean nestedAsynchronous) { - if (address.isGroup()) return getGroupRecipientDetails(context, address, groupRecord, settings, nestedAsynchronous); - else return getIndividualRecipientDetails(context, address, settings); - } - - private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, @NonNull Address address, Optional settings) { - if (!settings.isPresent()) { - settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(address); - } - - if (!settings.isPresent() && STATIC_DETAILS.containsKey(address.serialize())) { - return STATIC_DETAILS.get(address.serialize()); - } else { - boolean systemContact = settings.isPresent() && !TextUtils.isEmpty(settings.get().getSystemDisplayName()); - boolean isLocalNumber = address.serialize().equals(TextSecurePreferences.getLocalNumber(context)); - return new RecipientDetails(null, null, systemContact, isLocalNumber, settings.orNull(), null); - } - } - - private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional groupRecord, Optional settings, boolean asynchronous) { - - if (!groupRecord.isPresent()) { - groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.toGroupString()); - } - - if (!settings.isPresent()) { - settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(groupId); - } - - if (groupRecord.isPresent()) { - String title = groupRecord.get().getTitle(); - List
memberAddresses = groupRecord.get().getMembers(); - List members = new LinkedList<>(); - Long avatarId = null; - - for (Address memberAddress : memberAddresses) { - members.add(getRecipient(context, memberAddress, Optional.absent(), Optional.absent(), asynchronous)); - } - - if (!groupId.isMmsGroup() && title == null) { - title = context.getString(R.string.RecipientProvider_unnamed_group); - } - - if (groupRecord.get().getAvatar() != null && groupRecord.get().getAvatar().length > 0) { - avatarId = groupRecord.get().getAvatarId(); - } - - return new RecipientDetails(title, avatarId, false, false, settings.orNull(), members); - } - - return new RecipientDetails(context.getString(R.string.RecipientProvider_unnamed_group), null, false, false, settings.orNull(), null); - } - - static class RecipientDetails { - @Nullable final String name; - @Nullable final String customLabel; - @Nullable final Uri systemContactPhoto; - @Nullable final Uri contactUri; - @Nullable final Long groupAvatarId; - @Nullable final MaterialColor color; - @Nullable final Uri messageRingtone; - @Nullable final Uri callRingtone; - final long mutedUntil; - @Nullable final VibrateState messageVibrateState; - @Nullable final VibrateState callVibrateState; - final boolean blocked; - final int expireMessages; - @NonNull final List participants; - @Nullable final String profileName; - final boolean seenInviteReminder; - final Optional defaultSubscriptionId; - @NonNull final RegisteredState registered; - @Nullable final byte[] profileKey; - @Nullable final String profileAvatar; - final boolean profileSharing; - final boolean systemContact; - final boolean isLocalNumber; - @Nullable final String notificationChannel; - @NonNull final UnidentifiedAccessMode unidentifiedAccessMode; - final boolean forceSmsSelection; - - RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, - boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings, - @Nullable List participants) - { - this.groupAvatarId = groupAvatarId; - this.systemContactPhoto = settings != null ? Util.uri(settings.getSystemContactPhotoUri()) : null; - this.customLabel = settings != null ? settings.getSystemPhoneLabel() : null; - this.contactUri = settings != null ? Util.uri(settings.getSystemContactUri()) : null; - this.color = settings != null ? settings.getColor() : null; - this.messageRingtone = settings != null ? settings.getMessageRingtone() : null; - this.callRingtone = settings != null ? settings.getCallRingtone() : null; - this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; - this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null; - this.callVibrateState = settings != null ? settings.getCallVibrateState() : null; - this.blocked = settings != null && settings.isBlocked(); - this.expireMessages = settings != null ? settings.getExpireMessages() : 0; - this.participants = participants == null ? new LinkedList<>() : participants; - this.profileName = settings != null ? settings.getProfileName() : null; - this.seenInviteReminder = settings != null && settings.hasSeenInviteReminder(); - this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent(); - this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN; - this.profileKey = settings != null ? settings.getProfileKey() : null; - this.profileAvatar = settings != null ? settings.getProfileAvatar() : null; - this.profileSharing = settings != null && settings.isProfileSharing(); - this.systemContact = systemContact; - this.isLocalNumber = isLocalNumber; - this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; - this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED; - this.forceSmsSelection = settings != null && settings.isForceSmsSelection(); - - if (name == null && settings != null) this.name = settings.getSystemDisplayName(); - else this.name = name; - } - } - - private static class RecipientCache { - - private final Map cache = new SoftHashMap<>(1000); - - public synchronized Recipient get(Address address) { - return cache.get(address); - } - - public synchronized void set(Address address, Recipient recipient) { - cache.put(address, recipient); - } - - } - -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/search/SearchFragment.java b/src/org/thoughtcrime/securesms/search/SearchFragment.java index e7b8414cf8..d6f2e72dfe 100644 --- a/src/org/thoughtcrime/securesms/search/SearchFragment.java +++ b/src/org/thoughtcrime/securesms/search/SearchFragment.java @@ -135,7 +135,7 @@ public class SearchFragment extends Fragment implements SearchListAdapter.EventL @Override public void onContactClicked(@NonNull Recipient contact) { Intent intent = new Intent(getContext(), ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, contact.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, contact.getId()); long existingThread = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdIfExistsFor(contact); diff --git a/src/org/thoughtcrime/securesms/search/SearchRepository.java b/src/org/thoughtcrime/securesms/search/SearchRepository.java index 0f21d93d90..69e0bf1ef5 100644 --- a/src/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/src/org/thoughtcrime/securesms/search/SearchRepository.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.search.model.MessageResult; import org.thoughtcrime.securesms.search.model.SearchResult; import org.thoughtcrime.securesms.util.Stopwatch; @@ -132,10 +133,10 @@ public class SearchRepository { } private CursorList queryConversations(@NonNull String query) { - List numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query); - List
addresses = Stream.of(numbers).map(number -> Address.fromExternal(context, number)).toList(); + List numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query); + List recipientIds = Stream.of(numbers).map(number -> Recipient.external(context, number)).map(Recipient::getId).toList(); - Cursor conversations = threadDatabase.getFilteredConversationList(addresses); + Cursor conversations = threadDatabase.getFilteredConversationList(recipientIds); return conversations != null ? new CursorList<>(conversations, new ThreadModelBuilder(threadDatabase)) : CursorList.emptyList(); } @@ -184,8 +185,7 @@ public class SearchRepository { @Override public Recipient build(@NonNull Cursor cursor) { - Address address = Address.fromExternal(context, cursor.getString(1)); - return Recipient.from(context, address, false); + return Recipient.external(context, cursor.getString(1)); } } @@ -213,13 +213,13 @@ public class SearchRepository { @Override public MessageResult build(@NonNull Cursor cursor) { - Address conversationAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndex(SearchDatabase.CONVERSATION_ADDRESS))); - Address messageAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_ADDRESS))); - Recipient conversationRecipient = Recipient.from(context, conversationAddress, false); - Recipient messageRecipient = Recipient.from(context, messageAddress, false); - String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET)); - long receivedMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID)); + RecipientId conversationRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndex(SearchDatabase.CONVERSATION_RECIPIENT))); + RecipientId messageRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_RECIPIENT))); + Recipient conversationRecipient = Recipient.live(conversationRecipientId).get(); + Recipient messageRecipient = Recipient.live(messageRecipientId).get(); + String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET)); + long receivedMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID)); return new MessageResult(conversationRecipient, messageRecipient, body, threadId, receivedMs); } diff --git a/src/org/thoughtcrime/securesms/service/DirectShareService.java b/src/org/thoughtcrime/securesms/service/DirectShareService.java index 7604bb08f6..990c9efd72 100644 --- a/src/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/src/org/thoughtcrime/securesms/service/DirectShareService.java @@ -46,7 +46,7 @@ public class DirectShareService extends ChooserTargetService { ThreadRecord record; while ((record = reader.getNext()) != null && results.size() < 10) { - Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false); + Recipient recipient = Recipient.resolved(record.getRecipient().getId()); String name = recipient.toShortString(); Bitmap avatar; @@ -68,18 +68,13 @@ public class DirectShareService extends ChooserTargetService { avatar = getFallbackDrawable(recipient); } - Parcel parcel = Parcel.obtain(); - parcel.writeParcelable(recipient.getAddress(), 0); - Bundle bundle = new Bundle(); bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId()); - bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall()); + bundle.putString(ShareActivity.EXTRA_RECIPIENT_ID, recipient.getId().serialize()); bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType()); bundle.setClassLoader(getClassLoader()); results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle)); - parcel.recycle(); - } return results; diff --git a/src/org/thoughtcrime/securesms/service/QuickResponseService.java b/src/org/thoughtcrime/securesms/service/QuickResponseService.java index 96f61d5c0f..d45e23f0fc 100644 --- a/src/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/src/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -47,8 +47,7 @@ public class QuickResponseService extends IntentService { number = URLDecoder.decode(number); } - Address address = Address.fromExternal(this, number); - Recipient recipient = Recipient.from(this, address, false); + Recipient recipient = Recipient.external(this, number); int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); long expiresIn = recipient.getExpireMessages() * 1000L; diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 30140dbc32..3837eb39e9 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -38,6 +37,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ServiceUtil; @@ -121,7 +121,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe private static final String DATA_CHANNEL_NAME = "signaling"; - public static final String EXTRA_REMOTE_ADDRESS = "remote_address"; + public static final String EXTRA_REMOTE_RECIPIENT = "remote_recipient"; public static final String EXTRA_MUTE = "mute_value"; public static final String EXTRA_AVAILABLE = "enabled_value"; public static final String EXTRA_REMOTE_DESCRIPTION = "remote_description"; @@ -378,7 +378,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe boolean isSystemContact = false; if (Permissions.hasAny(WebRtcCallService.this, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { - isSystemContact = ContactAccessor.getInstance().isSystemContact(WebRtcCallService.this, recipient.getAddress().serialize()); + isSystemContact = ContactAccessor.getInstance().isSystemContact(WebRtcCallService.this, recipient.requireAddress().serialize()); } boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); @@ -436,7 +436,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe bluetoothStateManager.setWantsConnection(true); setCallInProgressNotification(TYPE_OUTGOING_RINGING, recipient); - DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(recipient.getAddress()); + DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(recipient.getId()); timeoutExecutor.schedule(new TimeoutRunnable(this.callId), 2, TimeUnit.MINUTES); @@ -683,7 +683,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); intent.setAction(ACTION_LOCAL_HANGUP); intent.putExtra(EXTRA_CALL_ID, intent.getLongExtra(EXTRA_CALL_ID, -1)); - intent.putExtra(EXTRA_REMOTE_ADDRESS, intent.getStringExtra(EXTRA_REMOTE_ADDRESS)); + intent.putExtra(EXTRA_REMOTE_RECIPIENT, (RecipientId) intent.getParcelableExtra(EXTRA_REMOTE_RECIPIENT)); startService(intent); } @@ -715,7 +715,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe } private void insertMissedCall(@NonNull Recipient recipient, boolean signal) { - Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getAddress()); + Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getId()); MessageNotifier.updateNotification(this, messageAndThreadId.second, signal); } @@ -729,14 +729,14 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe throw new AssertionError("assert"); } - DatabaseFactory.getSmsDatabase(this).insertReceivedCall(recipient.getAddress()); + DatabaseFactory.getSmsDatabase(this).insertReceivedCall(recipient.getId()); this.peerConnection.setAudioEnabled(true); this.peerConnection.setVideoEnabled(false); this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setConnected(Connected.newBuilder().setId(this.callId)).build().toByteArray()), false)); intent.putExtra(EXTRA_CALL_ID, callId); - intent.putExtra(EXTRA_REMOTE_ADDRESS, recipient.getAddress()); + intent.putExtra(EXTRA_REMOTE_RECIPIENT, recipient.getId()); handleCallConnected(intent); } @@ -753,7 +753,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setHangup(Hangup.newBuilder().setId(this.callId)).build().toByteArray()), false)); sendMessage(this.recipient, SignalServiceCallMessage.forHangup(new HangupMessage(this.callId))); - DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getAddress()); + DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getId()); this.terminate(); } @@ -1005,7 +1005,7 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe Callable callable = new Callable() { @Override public Boolean call() throws Exception { - messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()), + messageSender.sendCallMessage(new SignalServiceAddress(recipient.requireAddress().toPhoneString()), UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient), callMessage); return true; @@ -1032,10 +1032,10 @@ public class WebRtcCallService extends Service implements PeerConnection.Observe /// private @NonNull Recipient getRemoteRecipient(Intent intent) { - Address remoteAddress = intent.getParcelableExtra(EXTRA_REMOTE_ADDRESS); - if (remoteAddress == null) throw new AssertionError("No recipient in intent!"); + RecipientId recipientId = intent.getParcelableExtra(EXTRA_REMOTE_RECIPIENT); + if (recipientId == null) throw new AssertionError("No recipient in intent!"); - return Recipient.from(this, remoteAddress, true); + return Recipient.resolved(recipientId); } private long getCallId(Intent intent) { diff --git a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java index 8ce4bec8b4..c58c950410 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java @@ -1,13 +1,12 @@ package org.thoughtcrime.securesms.sms; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.SignalServiceGroup; public class IncomingJoinedMessage extends IncomingTextMessage { - public IncomingJoinedMessage(Address sender) { - super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0, false); + public IncomingJoinedMessage(RecipientId sender) { + super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0, false); } @Override diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 3d2e97d789..5c01d68000 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import android.telephony.SmsMessage; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; @@ -30,23 +31,23 @@ public class IncomingTextMessage implements Parcelable { }; private static final String TAG = IncomingTextMessage.class.getSimpleName(); - private final String message; - private Address sender; - private final int senderDeviceId; - private final int protocol; - private final String serviceCenterAddress; - private final boolean replyPathPresent; - private final String pseudoSubject; - private final long sentTimestampMillis; - private final Address groupId; - private final boolean push; - private final int subscriptionId; - private final long expiresInMillis; - private final boolean unidentified; + private final String message; + private RecipientId sender; + private final int senderDeviceId; + private final int protocol; + private final String serviceCenterAddress; + private final boolean replyPathPresent; + private final String pseudoSubject; + private final long sentTimestampMillis; + private final Address groupId; + private final boolean push; + private final int subscriptionId; + private final long expiresInMillis; + private final boolean unidentified; - public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) { + public IncomingTextMessage(@NonNull RecipientId sender, @NonNull SmsMessage message, int subscriptionId) { this.message = message.getDisplayMessageBody(); - this.sender = Address.fromExternal(context, message.getDisplayOriginatingAddress()); + this.sender = sender; this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; this.protocol = message.getProtocolIdentifier(); this.serviceCenterAddress = message.getServiceCenterAddress(); @@ -60,7 +61,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = false; } - public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, + public IncomingTextMessage(@NonNull RecipientId sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, long expiresInMillis, boolean unidentified) { @@ -138,7 +139,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = fragments.get(0).isUnidentified(); } - protected IncomingTextMessage(@NonNull Address sender, @Nullable Address groupId) + protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable Address groupId) { this.message = ""; this.sender = sender; @@ -179,7 +180,7 @@ public class IncomingTextMessage implements Parcelable { return new IncomingTextMessage(this, message); } - public Address getSender() { + public RecipientId getSender() { return sender; } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 10d8b122f7..4f03733cfb 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; @@ -54,6 +53,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; @@ -201,9 +201,9 @@ public class MessageSender { if (isLocalSelfSend(context, recipient, false)) { sendLocalMediaSelf(context, messageId); } else if (isGroupPushSend(recipient)) { - messageJobs.add(new PushGroupSendJob(messageId, recipient.getAddress(), null)); + messageJobs.add(new PushGroupSendJob(messageId, recipient.getId(), null)); } else { - messageJobs.add(new PushMediaSendJob(messageId, recipient.getAddress())); + messageJobs.add(new PushMediaSendJob(messageId, recipient)); } } @@ -228,9 +228,9 @@ public class MessageSender { } } - public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { + public static void resendGroupMessage(Context context, MessageRecord messageRecord, RecipientId filterRecipientId) { if (!messageRecord.isMms()) throw new AssertionError("Not Group"); - sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterAddress); + sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterRecipientId); } public static void resend(Context context, MessageRecord messageRecord) { @@ -275,22 +275,22 @@ public class MessageSender { private static void sendTextPush(Context context, Recipient recipient, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); + jobManager.add(new PushTextSendJob(messageId, recipient)); } private static void sendMediaPush(Context context, Recipient recipient, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress()); + PushMediaSendJob.enqueue(context, jobManager, messageId, recipient); } - private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) { + private static void sendGroupPush(Context context, Recipient recipient, long messageId, RecipientId filterRecipientId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), filterAddress); + PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientId); } private static void sendSms(Context context, Recipient recipient, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - jobManager.add(new SmsSendJob(context, messageId, recipient.getAddress())); + jobManager.add(new SmsSendJob(context, messageId, recipient)); } private static void sendMms(Context context, long messageId) { @@ -315,7 +315,7 @@ public class MessageSender { return false; } - if (recipient.isGroupRecipient()) { + if (recipient.isGroup()) { return false; } @@ -323,8 +323,8 @@ public class MessageSender { } private static boolean isGroupPushSend(Recipient recipient) { - return recipient.getAddress().isGroup() && - !recipient.getAddress().isMmsGroup(); + return recipient.requireAddress().isGroup() && + !recipient.requireAddress().isMmsGroup(); } private static boolean isPushDestination(Context context, Recipient destination) { @@ -335,13 +335,13 @@ public class MessageSender { } else { try { SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); - Optional registeredUser = accountManager.getContact(destination.getAddress().serialize()); + Optional registeredUser = accountManager.getContact(destination.requireAddress().serialize()); if (!registeredUser.isPresent()) { - DatabaseFactory.getRecipientDatabase(context).setRegistered(destination, RecipientDatabase.RegisteredState.NOT_REGISTERED); + DatabaseFactory.getRecipientDatabase(context).setRegistered(destination.getId(), RecipientDatabase.RegisteredState.NOT_REGISTERED); return false; } else { - DatabaseFactory.getRecipientDatabase(context).setRegistered(destination, RecipientDatabase.RegisteredState.REGISTERED); + DatabaseFactory.getRecipientDatabase(context).setRegistered(destination.getId(), RecipientDatabase.RegisteredState.REGISTERED); return true; } } catch (IOException e1) { @@ -365,7 +365,7 @@ public class MessageSender { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context); OutgoingMediaMessage message = mmsDatabase.getOutgoingMessage(messageId); - SyncMessageId syncId = new SyncMessageId(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), message.getSentTimeMillis()); + SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getSentTimeMillis()); for (Attachment attachment : message.getAttachments()) { attachmentDatabase.markAttachmentUploaded(messageId, attachment); @@ -392,7 +392,7 @@ public class MessageSender { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context); SmsMessageRecord message = smsDatabase.getMessage(messageId); - SyncMessageId syncId = new SyncMessageId(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), message.getDateSent()); + SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getDateSent()); smsDatabase.markAsSent(messageId, true); smsDatabase.markUnidentified(messageId, true); diff --git a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java index ef17f28f23..22c7671632 100644 --- a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java +++ b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java @@ -109,7 +109,7 @@ public class AttachmentUtil { try (Cursor messageCursor = DatabaseFactory.getMmsDatabase(context).getMessage(attachment.getMmsId())) { final MessageRecord message = DatabaseFactory.getMmsDatabase(context).readerFor(messageCursor).getNext(); - if (message == null || (!message.getRecipient().isSystemContact() && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress()))) { + if (message == null || (!message.getRecipient().isSystemContact() && !message.isOutgoing() && !message.getRecipient().isLocalNumber())) { return true; } } diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java index 6665be8bdc..c34814f10f 100644 --- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -43,7 +43,7 @@ public class CommunicationActions { .onAllGranted(() -> { Intent intent = new Intent(activity, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, recipient.getAddress()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId()); activity.startService(intent); Intent activityIntent = new Intent(activity, WebRtcCallActivity.class); @@ -71,7 +71,7 @@ public class CommunicationActions { @Override protected void onPostExecute(Long threadId) { Intent intent = new Intent(context, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); + intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()); diff --git a/src/org/thoughtcrime/securesms/util/DelimiterUtil.java b/src/org/thoughtcrime/securesms/util/DelimiterUtil.java index 174749e456..a338cc9f79 100644 --- a/src/org/thoughtcrime/securesms/util/DelimiterUtil.java +++ b/src/org/thoughtcrime/securesms/util/DelimiterUtil.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.util; +import android.text.TextUtils; + import java.util.regex.Pattern; public class DelimiterUtil { @@ -14,8 +16,11 @@ public class DelimiterUtil { } public static String[] split(String value, char delimiter) { - String regex = "(? newlyActiveUsers = refreshDirectory(context, AccountManagerFactory.createManager(context)); + List newlyActiveUsers = refreshDirectory(context, AccountManagerFactory.createManager(context)); if (TextSecurePreferences.isMultiDevice(context)) { ApplicationContext.getInstance(context) .getJobManager() - .add(new MultiDeviceContactUpdateJob(context)); + .add(new MultiDeviceContactUpdateJob()); } if (notifyOfNewUsers) notifyNewUsers(context, newlyActiveUsers); } @SuppressLint("CheckResult") - private static @NonNull List
refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager) + private static @NonNull List refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager) throws IOException { if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) { @@ -114,7 +115,7 @@ public class DirectoryHelper { if (!contactServiceResult.isPresent()) { Log.i(TAG, "[Batch] New contact discovery service failed, so we're skipping the comparison."); - return legacyResult.getNewlyActiveAddresses(); + return legacyResult.getNewlyActiveRecipients(); } if (legacyResult.getNumbers().size() == contactServiceResult.get().size() && legacyResult.getNumbers().containsAll(contactServiceResult.get())) { @@ -125,7 +126,7 @@ public class DirectoryHelper { accountManager.reportContactDiscoveryServiceMismatch(); } - return legacyResult.getNewlyActiveAddresses(); + return legacyResult.getNewlyActiveRecipients(); } catch (InterruptedException e) { throw new IOException("[Batch] Operation was interrupted.", e); @@ -147,7 +148,7 @@ public class DirectoryHelper { SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); Future legacyRequest = getLegacyRegisteredState(context, accountManager, recipientDatabase, recipient); - List>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, Collections.singleton(recipient.getAddress().serialize())); + List>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, Collections.singleton(recipient.requireAddress().serialize())); try { RegisteredState legacyState = legacyRequest.get(); @@ -182,11 +183,13 @@ public class DirectoryHelper { } } - private static void updateContactsDatabase(@NonNull Context context, @NonNull List
activeAddresses, boolean removeMissing) { + private static void updateContactsDatabase(@NonNull Context context, @NonNull List activeIds, boolean removeMissing) { Optional account = getOrCreateAccount(context); if (account.isPresent()) { try { + List
activeAddresses = Stream.of(activeIds).map(Recipient::resolved).map(Recipient::requireAddress).toList(); + DatabaseFactory.getContactsDatabase(context).removeDeletedRawContacts(account.get().getAccount()); DatabaseFactory.getContactsDatabase(context).setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing); @@ -198,14 +201,15 @@ public class DirectoryHelper { String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)); if (!TextUtils.isEmpty(number)) { - Address address = Address.fromExternal(context, number); - String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); - String contactPhotoUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); - String contactLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)); - Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)), - cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); + RecipientId recipientId = Recipient.external(context, number).getId(); + String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String contactPhotoUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); + String contactLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)); + Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)), + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); - handle.setSystemContactInfo(address, displayName, contactPhotoUri, contactLabel, contactUri.toString()); + + handle.setSystemContactInfo(recipientId, displayName, contactPhotoUri, contactLabel, contactUri.toString()); } } } finally { @@ -227,12 +231,13 @@ public class DirectoryHelper { } private static void notifyNewUsers(@NonNull Context context, - @NonNull List
newUsers) + @NonNull List newUsers) { if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return; - for (Address newUser: newUsers) { - if (!SessionUtil.hasSession(context, newUser) && !Util.isOwnNumber(context, newUser)) { + for (RecipientId newUser: newUsers) { + Recipient recipient = Recipient.resolved(newUser); + if (!SessionUtil.hasSession(context, recipient.requireAddress()) && !recipient.isLocalNumber()) { IncomingJoinedMessage message = new IncomingJoinedMessage(newUser); Optional insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message); @@ -287,34 +292,34 @@ public class DirectoryHelper { List activeTokens = accountManager.getContacts(eligibleContactNumbers); if (activeTokens != null) { - List
activeAddresses = new LinkedList<>(); - List
inactiveAddresses = new LinkedList<>(); + List activeIds = new LinkedList<>(); + List inactiveIds = new LinkedList<>(); Set inactiveContactNumbers = new HashSet<>(eligibleContactNumbers); for (ContactTokenDetails activeToken : activeTokens) { - activeAddresses.add(Address.fromSerialized(activeToken.getNumber())); + activeIds.add(recipientDatabase.getOrInsertFromE164(activeToken.getNumber())); inactiveContactNumbers.remove(activeToken.getNumber()); } for (String inactiveContactNumber : inactiveContactNumbers) { - inactiveAddresses.add(Address.fromSerialized(inactiveContactNumber)); + inactiveIds.add(recipientDatabase.getOrInsertFromE164(inactiveContactNumber)); } - Set
currentActiveAddresses = new HashSet<>(recipientDatabase.getRegistered()); - Set
contactAddresses = new HashSet<>(recipientDatabase.getSystemContacts()); - List
newlyActiveAddresses = Stream.of(activeAddresses) - .filter(address -> !currentActiveAddresses.contains(address)) - .filter(contactAddresses::contains) - .toList(); + Set currentActiveIds = new HashSet<>(recipientDatabase.getRegistered()); + Set contactIds = new HashSet<>(recipientDatabase.getSystemContacts()); + List newlyActiveIds = Stream.of(activeIds) + .filter(id -> !currentActiveIds.contains(id)) + .filter(contactIds::contains) + .toList(); - recipientDatabase.setRegistered(activeAddresses, inactiveAddresses); - updateContactsDatabase(context, activeAddresses, true); + recipientDatabase.setRegistered(activeIds, inactiveIds); + updateContactsDatabase(context, activeIds, true); - Set activeContactNumbers = Stream.of(activeAddresses).map(Address::serialize).collect(Collectors.toSet()); + Set activeContactNumbers = Stream.of(activeIds).map(Recipient::resolved).map(Recipient::requireAddress).map(Address::serialize).collect(Collectors.toSet()); if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context)) { - return new DirectoryResult(activeContactNumbers, newlyActiveAddresses); + return new DirectoryResult(activeContactNumbers, newlyActiveIds); } else { TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true); return new DirectoryResult(activeContactNumbers); @@ -332,27 +337,27 @@ public class DirectoryHelper { return SignalExecutors.UNBOUNDED.submit(() -> { boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED; boolean systemContact = recipient.isSystemContact(); - String number = recipient.getAddress().serialize(); + String number = recipient.requireAddress().serialize(); Optional details = accountManager.getContact(number); if (details.isPresent()) { - recipientDatabase.setRegistered(recipient, RegisteredState.REGISTERED); + recipientDatabase.setRegistered(recipient.getId(), RegisteredState.REGISTERED); if (Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) { - updateContactsDatabase(context, Util.asList(recipient.getAddress()), false); + updateContactsDatabase(context, Util.asList(recipient.getId()), false); } if (!activeUser && TextSecurePreferences.isMultiDevice(context)) { - ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context)); + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob()); } if (!activeUser && systemContact && !TextSecurePreferences.getNeedsSqlCipherMigration(context)) { - notifyNewUsers(context, Collections.singletonList(recipient.getAddress())); + notifyNewUsers(context, Collections.singletonList(recipient.getId())); } return RegisteredState.REGISTERED; } else { - recipientDatabase.setRegistered(recipient, RegisteredState.NOT_REGISTERED); + recipientDatabase.setRegistered(recipient.getId(), RegisteredState.NOT_REGISTERED); return RegisteredState.NOT_REGISTERED; } }); @@ -475,24 +480,24 @@ public class DirectoryHelper { private static class DirectoryResult { - private final Set numbers; - private final List
newlyActiveAddresses; + private final Set numbers; + private final List newlyActiveRecipients; DirectoryResult(@NonNull Set numbers) { this(numbers, Collections.emptyList()); } - DirectoryResult(@NonNull Set numbers, @NonNull List
newlyActiveAddresses) { - this.numbers = numbers; - this.newlyActiveAddresses = newlyActiveAddresses; + DirectoryResult(@NonNull Set numbers, @NonNull List newlyActiveRecipients) { + this.numbers = numbers; + this.newlyActiveRecipients = newlyActiveRecipients; } Set getNumbers() { return numbers; } - List
getNewlyActiveAddresses() { - return newlyActiveAddresses; + List getNewlyActiveRecipients() { + return newlyActiveRecipients; } } diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java index d64de8220d..d4d00c9d39 100644 --- a/src/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java @@ -8,13 +8,12 @@ import androidx.annotation.WorkerThread; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; @@ -52,7 +51,7 @@ public class GroupUtil { @WorkerThread public static Optional createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) { - String encodedGroupId = groupRecipient.getAddress().toGroupString(); + String encodedGroupId = groupRecipient.requireAddress().toGroupString(); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); if (!groupDatabase.isActive(encodedGroupId)) { @@ -107,7 +106,7 @@ public class GroupUtil { this.members = new LinkedList<>(); for (String member : groupContext.getMembersList()) { - this.members.add(Recipient.from(context, Address.fromExternal(context, member), true)); + this.members.add(Recipient.external(context, member)); } } } @@ -137,10 +136,18 @@ public class GroupUtil { return description.toString(); } - public void addListener(RecipientModifiedListener listener) { + public void addObserver(RecipientForeverObserver listener) { if (this.members != null) { for (Recipient member : this.members) { - member.addListener(listener); + member.live().observeForever(listener); + } + } + } + + public void removeObserver(RecipientForeverObserver listener) { + if (this.members != null) { + for (Recipient member : this.members) { + member.live().removeForeverObserver(listener); } } } diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index d6d66ce9e5..aeba030cf9 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityVerifiedMessage; @@ -52,7 +53,7 @@ public class IdentityUtil { @Override protected Optional doInBackground(Recipient... recipient) { return DatabaseFactory.getIdentityDatabase(context) - .getIdentity(recipient[0].getAddress()); + .getIdentity(recipient[0].getId()); } @Override @@ -74,19 +75,20 @@ public class IdentityUtil { GroupDatabase.GroupRecord groupRecord; while ((groupRecord = reader.getNext()) != null) { - if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive() && !groupRecord.isMms()) { + if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(group), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); smsDatabase.insertMessageInbox(incoming); } else { - Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(group.getGroupId(), false)), true); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(group.getGroupId(), false)); + Recipient groupRecipient = Recipient.resolved(recipientId); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); OutgoingTextMessage outgoing ; if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient); @@ -98,7 +100,7 @@ public class IdentityUtil { } if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.absent(), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); @@ -126,16 +128,16 @@ public class IdentityUtil { GroupDatabase.GroupRecord groupRecord; while ((groupRecord = reader.getNext()) != null) { - if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) { + if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive()) { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(group), 0, false); IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming); smsDatabase.insertMessageInbox(groupUpdate); } } - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.absent(), 0, false); IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming); Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate); @@ -164,8 +166,8 @@ public class IdentityUtil { public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) { synchronized (SESSION_LOCK) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - Recipient recipient = Recipient.from(context, Address.fromExternal(context, verifiedMessage.getDestination()), true); - Optional identityRecord = identityDatabase.getIdentity(recipient.getAddress()); + Recipient recipient = Recipient.external(context, verifiedMessage.getDestination()); + Optional identityRecord = identityDatabase.getIdentity(recipient.getId()); if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) { Log.w(TAG, "No existing record for default status"); @@ -177,7 +179,7 @@ public class IdentityUtil { identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT) { - identityDatabase.setVerified(recipient.getAddress(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT); + identityDatabase.setVerified(recipient.getId(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT); markIdentityVerified(context, recipient, false, true); } @@ -187,7 +189,7 @@ public class IdentityUtil { (identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED))) { saveIdentity(context, verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()); - identityDatabase.setVerified(recipient.getAddress(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED); + identityDatabase.setVerified(recipient.getId(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED); markIdentityVerified(context, recipient, true, true); } } diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java index 8f1d244edb..a9df4960d0 100644 --- a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java +++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java @@ -106,7 +106,7 @@ public class SelectedRecipientsAdapter extends BaseAdapter { ImageButton delete = (ImageButton) v.findViewById(R.id.delete); name.setText(p.getName()); - phone.setText(p.getAddress().serialize()); + phone.setText(p.requireAddress().serialize()); delete.setVisibility(modifiable ? View.VISIBLE : View.GONE); delete.setOnClickListener(new View.OnClickListener() { @Override diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 21b7465d65..7b3419cc03 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -103,11 +103,21 @@ public class Util { } public static String join(long[] list, String delimeter) { + List boxed = new ArrayList<>(list.length); + + for (int i = 0; i < list.length; i++) { + boxed.add(list[i]); + } + + return join(boxed, delimeter); + } + + public static String join(List list, String delimeter) { StringBuilder sb = new StringBuilder(); - for (int j=0;j