diff --git a/build.gradle b/build.gradle index 559d2ed42..da29c041c 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ dependencies { implementation 'org.conscrypt:conscrypt-android:2.0.0' implementation 'org.signal:aesgcmprovider:0.0.3' - implementation 'org.whispersystems:signal-service-android:2.13.9' + implementation 'org.whispersystems:signal-service-android:2.14.0' implementation 'org.signal:ringrtc-android:0.1.7.2' diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java index 277e4f74b..c602e5427 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java @@ -5,7 +5,6 @@ import androidx.annotation.Nullable; import android.view.View; import org.thoughtcrime.securesms.contactshare.Contact; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.linkpreview.LinkPreview; diff --git a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java index f3a0f12d4..69a2b5081 100644 --- a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.VerifySpan; @@ -95,7 +96,7 @@ public class ConfirmIdentityDialog extends AlertDialog { @Override protected Void doInBackground(Void... params) { synchronized (SESSION_LOCK) { - SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireAddress().toPhoneString(), 1); + SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext()); identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true); @@ -167,7 +168,7 @@ public class ConfirmIdentityDialog extends AlertDialog { boolean legacy = !messageRecord.isContentBundleKeyExchange(); SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, - messageRecord.getIndividualRecipient().requireAddress().toPhoneString(), + RecipientUtil.toSignalServiceAddress(getContext(), messageRecord.getIndividualRecipient()), messageRecord.getRecipientDeviceId(), messageRecord.getDateSent(), legacy ? Base64.decode(messageRecord.getBody()) : null, diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 1b762bd49..e0d5ccdda 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -205,7 +205,7 @@ public class ConversationListItem extends RelativeLayout fromView.setText(contact); fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), new SpannableString(fromView.getText()), highlightSubstring)); - subjectView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), contact.requireAddress().toString(), highlightSubstring)); + subjectView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), contact.getE164().or(""), highlightSubstring)); dateView.setText(""); archivedView.setVisibility(GONE); unreadIndicator.setVisibility(GONE); diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index 2ad9d0c92..8edcee7ce 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -36,9 +36,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiToggle; import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob; import org.thoughtcrime.securesms.logging.Log; diff --git a/src/org/thoughtcrime/securesms/DeviceActivity.java b/src/org/thoughtcrime/securesms/DeviceActivity.java index fdb7549a2..52e620c0c 100644 --- a/src/org/thoughtcrime/securesms/DeviceActivity.java +++ b/src/org/thoughtcrime/securesms/DeviceActivity.java @@ -20,6 +20,7 @@ import androidx.core.content.ContextCompat; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.push.AccountManagerFactory; @@ -177,7 +178,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity try { Context context = DeviceActivity.this; - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); String verificationCode = accountManager.getNewDeviceVerificationCode(); String ephemeralId = uri.getQueryParameter("uuid"); String publicKeyEncoded = uri.getQueryParameter("pub_key"); diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index bd6ba8712..06370e554 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -52,7 +52,6 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; -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; @@ -93,7 +92,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private final static String TAG = GroupCreateActivity.class.getSimpleName(); - public static final String GROUP_ADDRESS_EXTRA = "group_recipient"; + public static final String GROUP_ID_EXTRA = "group_id"; public static final String GROUP_THREAD_EXTRA = "group_thread"; private final DynamicTheme dynamicTheme = new DynamicTheme(); @@ -203,10 +202,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private void initializeExistingGroup() { - final Address groupAddress = getIntent().getParcelableExtra(GROUP_ADDRESS_EXTRA); + final String groupId = getIntent().getStringExtra(GROUP_ID_EXTRA); - if (groupAddress != null) { - new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupAddress.toGroupString()); + if (groupId != null) { + new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupId); } } @@ -458,7 +457,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().requireAddress()); + intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId()); activity.setResult(RESULT_OK, intent); activity.finish(); } @@ -501,7 +500,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.requireAddress().serialize())) { + } else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getE164().or(""))) { 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 3924ee9d4..4f20df653 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.requireAddress().toGroupString(), true); + return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), true); } @Override @@ -90,7 +90,7 @@ public class GroupMembersDialog extends AsyncTask> { public GroupMembers(List recipients) { for (Recipient recipient : recipients) { - if (isLocalNumber(recipient)) { + if (recipient.isLocalNumber()) { members.push(recipient); } else { members.add(recipient); @@ -102,7 +102,7 @@ public class GroupMembersDialog extends AsyncTask> { List recipientStrings = new LinkedList<>(); for (Recipient recipient : members) { - if (isLocalNumber(recipient)) { + if (recipient.isLocalNumber()) { recipientStrings.add(context.getString(R.string.GroupMembersDialog_me)); } else { String name = recipient.toShortString(); @@ -121,9 +121,5 @@ public class GroupMembersDialog extends AsyncTask> { public Recipient get(int index) { return members.get(index); } - - private boolean isLocalNumber(Recipient recipient) { - return Util.isOwnNumber(context, recipient.requireAddress()); - } } } diff --git a/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java b/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java index 1e9d0e2ff..a42b619ba 100644 --- a/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/IncomingMessageProcessor.java @@ -5,7 +5,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import java.io.Closeable; +import java.io.IOException; import java.util.Locale; import java.util.concurrent.locks.ReentrantLock; @@ -81,12 +82,7 @@ public class IncomingMessageProcessor { */ public @Nullable String processEnvelope(@NonNull SignalServiceEnvelope envelope) { if (envelope.hasSource()) { - Recipient recipient = Recipient.external(context, envelope.getSource()); - - if (!isActiveNumber(recipient)) { - recipientDatabase.setRegistered(recipient.getId(), RecipientDatabase.RegisteredState.REGISTERED); - jobManager.add(new DirectoryRefreshJob(recipient, false)); - } + Recipient.externalPush(context, envelope.getSourceAddress()); } if (envelope.isReceipt()) { @@ -113,14 +109,10 @@ 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(Recipient.external(context, envelope.getSource()).getId(), envelope.getTimestamp()), + mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(Recipient.externalPush(context, envelope.getSourceAddress()).getId(), envelope.getTimestamp()), System.currentTimeMillis()); } - private boolean isActiveNumber(@NonNull Recipient recipient) { - return recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED; - } - @Override public void close() { release(); diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index 4118dc097..da42f06c7 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -52,7 +52,6 @@ import androidx.viewpager.widget.ViewPager; import org.thoughtcrime.securesms.animation.DepthPageTransformer; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index 1f5255419..6cf6deab5 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -368,7 +368,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity List receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId()); if (receiptInfoList.isEmpty()) { - List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireAddress().toGroupString(), false); + List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireGroupId(), false); for (Recipient recipient : group) { recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1)); diff --git a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java index 6b20a1410..12a290cc8 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java @@ -11,7 +11,9 @@ import android.widget.BaseAdapter; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Conversions; +import org.thoughtcrime.securesms.util.adapter.StableIdGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -19,11 +21,12 @@ import java.util.List; class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener { - private final Context context; - private final GlideRequests glideRequests; - private final MessageRecord record; - private final List members; - private final boolean isPushGroup; + private final Context context; + private final GlideRequests glideRequests; + private final MessageRecord record; + private final List members; + private final boolean isPushGroup; + private final StableIdGenerator idGenerator; MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @NonNull MessageRecord record, @NonNull List members, @@ -34,6 +37,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView. this.record = record; this.isPushGroup = isPushGroup; this.members = members; + this.idGenerator = new StableIdGenerator<>(); } @Override @@ -48,11 +52,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.requireAddress().serialize().getBytes())); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } + return idGenerator.getId(members.get(position).recipient.getId()); } @Override diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index a5bde3bb2..746aa86d4 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColors; import org.thoughtcrime.securesms.components.ThreadPhotoRailView; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; @@ -421,10 +420,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi if (aboutDivider != null) aboutDivider.setVisible(false); if (divider != null) divider.setVisible(false); } else { - colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(getActivity())); - colorPreference.setColor(recipient.getColor().toActionBarColor(getActivity())); + colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(requireActivity())); + colorPreference.setColor(recipient.getColor().toActionBarColor(requireActivity())); - aboutPreference.setTitle(formatAddress(recipient.requireAddress())); + aboutPreference.setTitle(formatRecipient(recipient)); aboutPreference.setSummary(recipient.getCustomLabel()); aboutPreference.setSecure(recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED); @@ -453,10 +452,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } } - private @NonNull String formatAddress(@NonNull Address address) { - if (address.isPhone()) return PhoneNumberUtils.formatNumber(address.toPhoneString()); - else if (address.isEmail()) return address.toEmailString(); - else return ""; + private @NonNull String formatRecipient(@NonNull Recipient recipient) { + if (recipient.getE164().isPresent()) return PhoneNumberUtils.formatNumber(recipient.requireE164()); + else if (recipient.getEmail().isPresent()) return recipient.requireEmail(); + else return ""; } private @NonNull String getRingtoneSummary(@NonNull Context context, @Nullable Uri ringtone) { @@ -697,7 +696,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi if (recipient.get().isGroup()) { bodyRes = R.string.RecipientPreferenceActivity_block_and_leave_group_description; - if (recipient.get().isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.get().requireAddress().toGroupString())) { + if (recipient.get().isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.get().requireGroupId())) { titleRes = R.string.RecipientPreferenceActivity_block_and_leave_group; } else { titleRes = R.string.RecipientPreferenceActivity_block_group; @@ -745,7 +744,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi DatabaseFactory.getRecipientDatabase(context) .setBlocked(recipient.getId(), blocked); - if (recipient.isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireAddress().toGroupString())) { + if (recipient.isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); Optional leaveMessage = GroupUtil.createGroupLeaveMessage(context, recipient); @@ -753,7 +752,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi MessageSender.send(context, leaveMessage.get(), threadId, false, null); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - String groupId = recipient.requireAddress().toGroupString(); + String groupId = recipient.requireGroupId(); groupDatabase.setActive(groupId, false); groupDatabase.remove(groupId, Recipient.self().getId()); } else { @@ -790,7 +789,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi public void onInSecureCallClicked() { try { Intent dialIntent = new Intent(Intent.ACTION_DIAL, - Uri.parse("tel:" + recipient.get().requireAddress().serialize())); + Uri.parse("tel:" + recipient.get().requireE164())); startActivity(dialIntent); } catch (ActivityNotFoundException anfe) { Log.w(TAG, anfe); diff --git a/src/org/thoughtcrime/securesms/SmsSendtoActivity.java b/src/org/thoughtcrime/securesms/SmsSendtoActivity.java index 75326ba5c..28c55b6c2 100644 --- a/src/org/thoughtcrime/securesms/SmsSendtoActivity.java +++ b/src/org/thoughtcrime/securesms/SmsSendtoActivity.java @@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.logging.Log; import android.widget.Toast; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Rfc5724Uri; diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 5dc7de1de..a30bc25d3 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -269,12 +269,16 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity 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.localNumber = TextSecurePreferences.getLocalNumber(requireContext()); this.localIdentity = localIdentityParcelable.get(); this.recipient = Recipient.live(recipientId); - this.remoteNumber = recipient.get().requireAddress().serialize(); + this.remoteNumber = recipient.resolve().getE164().or(""); this.remoteIdentity = remoteIdentityParcelable.get(); + if (TextUtils.isEmpty(remoteNumber)) { + Log.w(TAG, "Empty remote number! Did we get a UUID-only message somehow?"); + } + this.recipient.observe(this, this::setRecipientText); new AsyncTask() { @@ -573,7 +577,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Recipient... params) { synchronized (SESSION_LOCK) { if (isChecked) { - Log.i(TAG, "Saving identity: " + params[0].requireAddress()); + Log.i(TAG, "Saving identity: " + params[0].getId()); DatabaseFactory.getIdentityDatabase(getActivity()) .saveIdentity(params[0].getId(), remoteIdentity, diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java index 385153ffe..65b2514c7 100644 --- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -281,7 +281,7 @@ public class WebRtcCallActivity extends Activity { public void onClick(View v) { synchronized (SESSION_LOCK) { TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this); - identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.requireAddress().serialize(), 1), theirIdentity, true); + identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.requireServiceId(), 1), theirIdentity, true); } Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class); diff --git a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java index 734493445..5dcaf0dec 100644 --- a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java +++ b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java @@ -20,12 +20,10 @@ import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement; import org.thoughtcrime.securesms.backup.BackupProtos.Sticker; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; -import org.thoughtcrime.securesms.database.Address; 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.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; diff --git a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java index 57e34f4c5..3c7f4055f 100644 --- a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java +++ b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java @@ -31,7 +31,6 @@ 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.RecipientForeverObserver; diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java index ec04b64b3..9ab2c5d78 100644 --- a/src/org/thoughtcrime/securesms/components/QuoteView.java +++ b/src/org/thoughtcrime/securesms/components/QuoteView.java @@ -187,11 +187,10 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver { } private void setQuoteAuthor(@NonNull Recipient author) { - boolean outgoing = messageType != MESSAGE_TYPE_INCOMING; - boolean isOwnNumber = Util.isOwnNumber(getContext(), author.requireAddress()); + boolean outgoing = messageType != MESSAGE_TYPE_INCOMING; - authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) - : author.toShortString()); + authorView.setText(author.isLocalNumber() ? getContext().getString(R.string.QuoteView_you) + : author.toShortString()); // We use the raw color resource because Android 4.x was struggling with tints here quoteBarView.setImageResource(author.getColor().toQuoteBarColorResource(getContext(), outgoing)); diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java b/src/org/thoughtcrime/securesms/components/TypingStatusRepository.java index 1a5d280b5..737be9da7 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.requireAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { + if (author.isLocalNumber()) { 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.requireAddress().serialize().equals(TextSecurePreferences.getLocalNumber(context))) { + if (author.isLocalNumber()) { return; } diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java index 5631f0aee..9faffb6d2 100644 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java @@ -308,9 +308,10 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs this.name.setText(recipient.getName()); if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) { - this.phoneNumber.setText(recipient.requireAddress().serialize() + " (~" + recipient.getProfileName() + ")"); + // TODO [greyson] This will need to change in UI PR? + this.phoneNumber.setText(recipient.requireE164() + " (~" + recipient.getProfileName() + ")"); } else { - this.phoneNumber.setText(recipient.requireAddress().serialize()); + this.phoneNumber.setText(recipient.requireE164()); } } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java index 5de0ca5b0..afd3a16b5 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -32,7 +32,6 @@ 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; @@ -70,13 +69,13 @@ public class ContactAccessor { return instance; } - public Set
getAllContactsWithNumbers(Context context) { - Set
results = new HashSet<>(); + public Set getAllContactsWithNumbers(Context context) { + Set results = new HashSet<>(); 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.fromSerialized(PhoneNumberFormatter.get(context).format(cursor.getString(0)))); + results.add(PhoneNumberFormatter.get(context).format(cursor.getString(0))); } } } @@ -109,17 +108,21 @@ public class ContactAccessor { final ContentResolver resolver = context.getContentResolver(); final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME}; - final List
registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered()).map(Recipient::resolved).map(Recipient::requireAddress).toList(); + final List registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered()) + .map(Recipient::resolved) + .filter(r -> r.getE164().isPresent()) + .map(Recipient::requireE164) + .toList(); final Collection lookupData = new ArrayList<>(registeredAddresses.size()); - for (Address registeredAddress : registeredAddresses) { - Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress.serialize())); + for (String registeredAddress : registeredAddresses) { + Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress)); Cursor lookupCursor = resolver.query(uri, inProjection, null, null, null); try { if (lookupCursor != null && lookupCursor.moveToFirst()) { final ContactData contactData = new ContactData(lookupCursor.getLong(0), lookupCursor.getString(1)); - contactData.numbers.add(new NumberData("TextSecure", registeredAddress.serialize())); + contactData.numbers.add(new NumberData("TextSecure", registeredAddress)); lookupData.add(contactData); } } finally { diff --git a/src/org/thoughtcrime/securesms/contacts/ContactRepository.java b/src/org/thoughtcrime/securesms/contacts/ContactRepository.java index 0f4787242..cc4d1a5d2 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactRepository.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactRepository.java @@ -88,12 +88,12 @@ public class ContactRepository { if (noteToSelfTitle.toLowerCase().contains(query.toLowerCase())) { Recipient self = Recipient.self(); boolean nameMatch = self.getDisplayName().toLowerCase().contains(query.toLowerCase()); - boolean numberMatch = self.requireAddress().serialize() != null && self.requireAddress().serialize().contains(query); + boolean numberMatch = self.getE164().isPresent() && self.requireE164().contains(query); boolean shouldAdd = !nameMatch && !numberMatch; if (shouldAdd) { MatrixCursor selfCursor = new MatrixCursor(RecipientDatabase.SEARCH_PROJECTION); - selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.requireAddress().serialize(), null, null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle }); + selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle }); cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor }); } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java index 5c3ba05a3..bf4b6df90 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java @@ -14,14 +14,12 @@ import android.widget.TextView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.FromTextView; -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.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; public class ContactSelectionListItem extends LinearLayout implements RecipientForeverObserver { diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 5054ff431..8b9d0f501 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -183,7 +183,7 @@ public class ContactsCursorLoader extends CursorLoader { while ((threadRecord = reader.getNext()) != null) { recentConversations.addRow(new Object[] { threadRecord.getRecipient().getId().serialize(), threadRecord.getRecipient().toShortString(), - threadRecord.getRecipient().requireAddress().serialize(), + threadRecord.getRecipient().requireStringId(), ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactRepository.RECENT_TYPE }); diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java index 26d1482a3..116b14332 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java @@ -31,7 +31,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; 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.Util; @@ -82,17 +81,17 @@ public class ContactsDatabase { } public synchronized void setRegisteredUsers(@NonNull Account account, - @NonNull List
registeredAddressList, + @NonNull List registeredAddressList, boolean remove) throws RemoteException, OperationApplicationException { - Set
registeredAddressSet = new HashSet<>(registeredAddressList); + Set registeredAddressSet = new HashSet<>(registeredAddressList); ArrayList operations = new ArrayList<>(); - Map currentContacts = getSignalRawContacts(account); - List> registeredChunks = Util.chunk(registeredAddressList, 50); + Map currentContacts = getSignalRawContacts(account); + List> registeredChunks = Util.chunk(registeredAddressList, 50); - for (List
registeredChunk : registeredChunks) { - for (Address registeredAddress : registeredChunk) { + for (List registeredChunk : registeredChunks) { + for (String registeredAddress : registeredChunk) { if (!currentContacts.containsKey(registeredAddress)) { Optional systemContactInfo = getSystemContactInfo(registeredAddress); @@ -109,7 +108,7 @@ public class ContactsDatabase { } } - for (Map.Entry currentContactEntry : currentContacts.entrySet()) { + for (Map.Entry currentContactEntry : currentContacts.entrySet()) { if (!registeredAddressSet.contains(currentContactEntry.getKey())) { if (remove) { Log.i(TAG, "Removing number: " + currentContactEntry.getKey()); @@ -240,7 +239,7 @@ public class ContactsDatabase { private void addContactVoiceSupport(List operations, - @NonNull Address address, long rawContactId) + @NonNull String address, long rawContactId) { operations.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) .withSelection(RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)}) @@ -250,9 +249,9 @@ public class ContactsDatabase { operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build()) .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId) .withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE) - .withValue(ContactsContract.Data.DATA1, address.toPhoneString()) + .withValue(ContactsContract.Data.DATA1, address) .withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name)) - .withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address.toPhoneString())) + .withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address)) .withYieldAllowed(true) .build()); } @@ -348,13 +347,13 @@ public class ContactsDatabase { .build()); } - private @NonNull Map getSignalRawContacts(@NonNull Account account) { + private @NonNull Map getSignalRawContacts(@NonNull Account account) { Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon() .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name) .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build(); - Map signalContacts = new HashMap<>(); - Cursor cursor = null; + Map signalContacts = new HashMap<>(); + Cursor cursor = null; try { String[] projection = new String[] {BaseColumns._ID, RawContacts.SYNC1, RawContacts.SYNC4, RawContacts.CONTACT_ID, RawContacts.DISPLAY_NAME_PRIMARY, RawContacts.DISPLAY_NAME_SOURCE}; @@ -362,7 +361,7 @@ public class ContactsDatabase { cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null); while (cursor != null && cursor.moveToNext()) { - Address currentAddress = Address.fromSerialized(PhoneNumberFormatter.get(context).format(cursor.getString(1))); + String currentAddress = PhoneNumberFormatter.get(context).format(cursor.getString(1)); long rawContactId = cursor.getLong(0); long contactId = cursor.getLong(3); String supportsVoice = cursor.getString(2); @@ -380,11 +379,9 @@ public class ContactsDatabase { return signalContacts; } - private Optional getSystemContactInfo(@NonNull Address address) + private Optional getSystemContactInfo(@NonNull String address) { - if (!address.isPhone()) return Optional.absent(); - - Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString())); + Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)); String[] projection = {ContactsContract.PhoneLookup.NUMBER, ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME}; @@ -395,8 +392,8 @@ public class ContactsDatabase { numberCursor = context.getContentResolver().query(uri, projection, null, null, null); while (numberCursor != null && numberCursor.moveToNext()) { - String systemNumber = numberCursor.getString(0); - Address systemAddress = Address.fromSerialized(PhoneNumberFormatter.get(context).format(systemNumber)); + String systemNumber = numberCursor.getString(0); + String systemAddress = 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 c231a39a7..5f4734e59 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.requireAddress().serialize(); + String number = c.getE164().or(c.getEmail()).or(""); 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.requireAddress().serialize()), 0, len, + s.setSpan(new Annotation("number", number), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return s; diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java index 7d0baf8a6..db505e0f5 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java @@ -6,7 +6,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.util.Conversions; @@ -19,24 +18,24 @@ import java.security.MessageDigest; public class GroupRecordContactPhoto implements ContactPhoto { - private final @NonNull Address address; - private final long avatarId; + private final String groupId; + private final long avatarId; - public GroupRecordContactPhoto(@NonNull Address address, long avatarId) { - this.address = address; + public GroupRecordContactPhoto(@NonNull String groupId, long avatarId) { + this.groupId = groupId; this.avatarId = avatarId; } @Override public InputStream openInputStream(Context context) throws IOException { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Optional groupRecord = groupDatabase.getGroup(address.toGroupString()); + Optional groupRecord = groupDatabase.getGroup(groupId); if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) { return new ByteArrayInputStream(groupRecord.get().getAvatar()); } - throw new IOException("Couldn't load avatar for group: " + address.toGroupString()); + throw new IOException("Couldn't load avatar for group: " + groupId); } @Override @@ -51,7 +50,7 @@ public class GroupRecordContactPhoto implements ContactPhoto { @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.serialize().getBytes()); + messageDigest.update(groupId.getBytes()); messageDigest.update(Conversions.longToByteArray(avatarId)); } @@ -60,12 +59,11 @@ public class GroupRecordContactPhoto implements ContactPhoto { if (other == null || !(other instanceof GroupRecordContactPhoto)) return false; GroupRecordContactPhoto that = (GroupRecordContactPhoto)other; - return this.address.equals(that.address) && this.avatarId == that.avatarId; + return this.groupId.equals(that.groupId) && this.avatarId == that.avatarId; } @Override public int hashCode() { - return this.address.hashCode() ^ (int) avatarId; + return this.groupId.hashCode() ^ (int) avatarId; } - } diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java index bca3e003d..76a16e585 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java @@ -7,7 +7,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.profiles.AvatarHelper; -import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import java.io.File; diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/SystemContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/SystemContactPhoto.java index 863598a5e..e5585254e 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/SystemContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/SystemContactPhoto.java @@ -6,7 +6,7 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Conversions; import java.io.FileNotFoundException; @@ -15,12 +15,12 @@ import java.security.MessageDigest; public class SystemContactPhoto implements ContactPhoto { - private final @NonNull Address address; - private final @NonNull Uri contactPhotoUri; - private final long lastModifiedTime; + private final RecipientId recipientId; + private final Uri contactPhotoUri; + private final long lastModifiedTime; - public SystemContactPhoto(@NonNull Address address, @NonNull Uri contactPhotoUri, long lastModifiedTime) { - this.address = address; + public SystemContactPhoto(@NonNull RecipientId recipientId, @NonNull Uri contactPhotoUri, long lastModifiedTime) { + this.recipientId = recipientId; this.contactPhotoUri = contactPhotoUri; this.lastModifiedTime = lastModifiedTime; } @@ -42,7 +42,7 @@ public class SystemContactPhoto implements ContactPhoto { @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.serialize().getBytes()); + messageDigest.update(recipientId.serialize().getBytes()); messageDigest.update(contactPhotoUri.toString().getBytes()); messageDigest.update(Conversions.longToByteArray(lastModifiedTime)); } @@ -53,12 +53,12 @@ public class SystemContactPhoto implements ContactPhoto { SystemContactPhoto that = (SystemContactPhoto)other; - return this.address.equals(that.address) && this.contactPhotoUri.equals(that.contactPhotoUri) && this.lastModifiedTime == that.lastModifiedTime; + return this.recipientId.equals(that.recipientId) && this.contactPhotoUri.equals(that.contactPhotoUri) && this.lastModifiedTime == that.lastModifiedTime; } @Override public int hashCode() { - return address.hashCode() ^ contactPhotoUri.hashCode() ^ (int)lastModifiedTime; + return recipientId.hashCode() ^ contactPhotoUri.hashCode() ^ (int)lastModifiedTime; } } diff --git a/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java b/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java index 794255503..a63ea6610 100644 --- a/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java +++ b/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java @@ -1,527 +1,35 @@ package org.thoughtcrime.securesms.contacts.sync; -import android.Manifest; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.Context; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import android.provider.ContactsContract; + import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; +import androidx.annotation.WorkerThread; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.logging.Log; - -import com.annimon.stream.Collectors; -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.SessionUtil; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; -import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; -import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.push.AccountManagerFactory; -import org.thoughtcrime.securesms.push.IasTrustStore; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.sms.IncomingJoinedMessage; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.ContactTokenDetails; -import org.whispersystems.signalservice.api.push.TrustStore; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; -import org.whispersystems.signalservice.internal.contacts.crypto.Quote; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; +import org.thoughtcrime.securesms.util.FeatureFlags; import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; public class DirectoryHelper { - private static final String TAG = DirectoryHelper.class.getSimpleName(); - - private static final int CONTACT_DISCOVERY_BATCH_SIZE = 2048; - - public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers) - throws IOException - { - if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return; - if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) return; - - List newlyActiveUsers = refreshDirectory(context, AccountManagerFactory.createManager(context)); - - if (TextSecurePreferences.isMultiDevice(context)) { - ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob()); - } - - if (notifyOfNewUsers) notifyNewUsers(context, newlyActiveUsers); - } - - @SuppressLint("CheckResult") - private static @NonNull List refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager) - throws IOException - { - if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) { - return Collections.emptyList(); - } - - if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) { - return Collections.emptyList(); - } - - RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); - Stream eligibleRecipientDatabaseContactNumbers = Stream.of(recipientDatabase.getAllAddresses()).filter(Address::isPhone).map(Address::toPhoneString); - Stream eligibleSystemDatabaseContactNumbers = Stream.of(ContactAccessor.getInstance().getAllContactsWithNumbers(context)).map(Address::serialize); - Set eligibleContactNumbers = Stream.concat(eligibleRecipientDatabaseContactNumbers, eligibleSystemDatabaseContactNumbers).collect(Collectors.toSet()); - - Future legacyRequest = getLegacyDirectoryResult(context, accountManager, recipientDatabase, eligibleContactNumbers); - List>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, eligibleContactNumbers); - - try { - DirectoryResult legacyResult = legacyRequest.get(); - Optional> contactServiceResult = executeAndMergeContactDiscoveryRequests(accountManager, contactServiceRequest); - - if (!contactServiceResult.isPresent()) { - Log.i(TAG, "[Batch] New contact discovery service failed, so we're skipping the comparison."); - return legacyResult.getNewlyActiveRecipients(); - } - - if (legacyResult.getNumbers().size() == contactServiceResult.get().size() && legacyResult.getNumbers().containsAll(contactServiceResult.get())) { - Log.i(TAG, "[Batch] New contact discovery service request matched existing results."); - accountManager.reportContactDiscoveryServiceMatch(); - } else { - Log.w(TAG, "[Batch] New contact discovery service request did NOT match existing results."); - accountManager.reportContactDiscoveryServiceMismatch(); - } - - return legacyResult.getNewlyActiveRecipients(); - - } catch (InterruptedException e) { - throw new IOException("[Batch] Operation was interrupted.", e); - } catch (ExecutionException e) { - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } else { - Log.e(TAG, "[Batch] Experienced an unexpected exception.", e); - throw new AssertionError(e); - } - } - } - - public static RegisteredState refreshDirectoryFor(@NonNull Context context, - @NonNull Recipient recipient) - throws IOException - { - RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); - - Future legacyRequest = getLegacyRegisteredState(context, accountManager, recipientDatabase, recipient); - List>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, Collections.singleton(recipient.requireAddress().serialize())); - - try { - RegisteredState legacyState = legacyRequest.get(); - Optional> contactServiceResult = executeAndMergeContactDiscoveryRequests(accountManager, contactServiceRequest); - - if (!contactServiceResult.isPresent()) { - Log.i(TAG, "[Singular] New contact discovery service failed, so we're skipping the comparison."); - return legacyState; - } - - RegisteredState contactServiceState = contactServiceResult.get().size() == 1 ? RegisteredState.REGISTERED : RegisteredState.NOT_REGISTERED; - - if (legacyState == contactServiceState) { - Log.i(TAG, "[Singular] New contact discovery service request matched existing results."); - accountManager.reportContactDiscoveryServiceMatch(); - } else { - Log.w(TAG, "[Singular] New contact discovery service request did NOT match existing results."); - accountManager.reportContactDiscoveryServiceMismatch(); - } - - return legacyState; - - } catch (InterruptedException e) { - throw new IOException("[Singular] Operation was interrupted.", e); - } catch (ExecutionException e) { - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } else { - Log.e(TAG, "[Singular] Experienced an unexpected exception.", e); - throw new AssertionError(e); - } - } - } - - 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); - - Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context); - RecipientDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientDatabase(context).resetAllSystemContactInfo(); - - try { - while (cursor != null && cursor.moveToNext()) { - String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)); - - if (!TextUtils.isEmpty(number)) { - 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)); - int phoneType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)); - Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)), - cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); - - - handle.setSystemContactInfo(recipientId, displayName, contactPhotoUri, contactLabel, phoneType, contactUri.toString()); - } - } - } finally { - handle.finish(); - } - - if (NotificationChannels.supported()) { - try (RecipientDatabase.RecipientReader recipients = DatabaseFactory.getRecipientDatabase(context).getRecipientsWithNotificationChannels()) { - Recipient recipient; - while ((recipient = recipients.getNext()) != null) { - NotificationChannels.updateContactChannelName(context, recipient); - } - } - } - } catch (RemoteException | OperationApplicationException e) { - Log.w(TAG, "Failed to update contacts.", e); - } - } - } - - private static void notifyNewUsers(@NonNull Context context, - @NonNull List newUsers) - { - if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return; - - 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); - - if (insertResult.isPresent()) { - int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); - if (hour >= 9 && hour < 23) { - MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), true); - } else { - MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), false); - } - } - } - } - } - - private static Optional getOrCreateAccount(Context context) { - AccountManager accountManager = AccountManager.get(context); - Account[] accounts = accountManager.getAccountsByType("org.thoughtcrime.securesms"); - - Optional account; - - if (accounts.length == 0) account = createAccount(context); - else account = Optional.of(new AccountHolder(accounts[0], false)); - - if (account.isPresent() && !ContentResolver.getSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY)) { - ContentResolver.setSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY, true); - } - - return account; - } - - private static Optional createAccount(Context context) { - AccountManager accountManager = AccountManager.get(context); - Account account = new Account(context.getString(R.string.app_name), "org.thoughtcrime.securesms"); - - if (accountManager.addAccountExplicitly(account, null, null)) { - Log.i(TAG, "Created new account..."); - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); - return Optional.of(new AccountHolder(account, true)); + @WorkerThread + public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers) throws IOException { + if (FeatureFlags.UUIDS) { + // TODO [greyson] Create a DirectoryHelperV2 when appropriate. + DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers); } else { - Log.w(TAG, "Failed to create account!"); - return Optional.absent(); + DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers); } } - private static Future getLegacyDirectoryResult(@NonNull Context context, - @NonNull SignalServiceAccountManager accountManager, - @NonNull RecipientDatabase recipientDatabase, - @NonNull Set eligibleContactNumbers) - { - return SignalExecutors.UNBOUNDED.submit(() -> { - List activeTokens = accountManager.getContacts(eligibleContactNumbers); - - if (activeTokens != null) { - List activeIds = new LinkedList<>(); - List inactiveIds = new LinkedList<>(); - - Set inactiveContactNumbers = new HashSet<>(eligibleContactNumbers); - - for (ContactTokenDetails activeToken : activeTokens) { - activeIds.add(recipientDatabase.getOrInsertFromE164(activeToken.getNumber())); - inactiveContactNumbers.remove(activeToken.getNumber()); - } - - for (String inactiveContactNumber : inactiveContactNumbers) { - inactiveIds.add(recipientDatabase.getOrInsertFromE164(inactiveContactNumber)); - } - - 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(activeIds, inactiveIds); - updateContactsDatabase(context, activeIds, true); - - 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, newlyActiveIds); - } else { - TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true); - return new DirectoryResult(activeContactNumbers); - } - } - return new DirectoryResult(Collections.emptySet(), Collections.emptyList()); - }); - } - - private static Future getLegacyRegisteredState(@NonNull Context context, - @NonNull SignalServiceAccountManager accountManager, - @NonNull RecipientDatabase recipientDatabase, - @NonNull Recipient recipient) - { - return SignalExecutors.UNBOUNDED.submit(() -> { - boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED; - boolean systemContact = recipient.isSystemContact(); - String number = recipient.requireAddress().serialize(); - Optional details = accountManager.getContact(number); - - if (details.isPresent()) { - recipientDatabase.setRegistered(recipient.getId(), RegisteredState.REGISTERED); - - if (Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) { - updateContactsDatabase(context, Util.asList(recipient.getId()), false); - } - - if (!activeUser && TextSecurePreferences.isMultiDevice(context)) { - ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob()); - } - - if (!activeUser && systemContact && !TextSecurePreferences.getNeedsSqlCipherMigration(context)) { - notifyNewUsers(context, Collections.singletonList(recipient.getId())); - } - - return RegisteredState.REGISTERED; - } else { - recipientDatabase.setRegistered(recipient.getId(), RegisteredState.NOT_REGISTERED); - return RegisteredState.NOT_REGISTERED; - } - }); - } - - private static List>> getContactServiceDirectoryResult(@NonNull Context context, - @NonNull SignalServiceAccountManager accountManager, - @NonNull Set eligibleContactNumbers) - { - Set sanitizedNumbers = sanitizeNumbers(eligibleContactNumbers); - List> batches = splitIntoBatches(sanitizedNumbers, CONTACT_DISCOVERY_BATCH_SIZE); - List>> futures = new ArrayList<>(batches.size()); - KeyStore iasKeyStore = getIasKeyStore(context); - - for (Set batch : batches) { - Future> future = SignalExecutors.UNBOUNDED.submit(() -> { - return new HashSet<>(accountManager.getRegisteredUsers(iasKeyStore, batch, BuildConfig.MRENCLAVE)); - }); - futures.add(future); - } - return futures; - } - - private static Set sanitizeNumbers(@NonNull Set numbers) { - return Stream.of(numbers).filter(number -> { - try { - return number.startsWith("+") && number.length() > 1 && number.charAt(1) != '0' && Long.parseLong(number.substring(1)) > 0; - } catch (NumberFormatException e) { - return false; - } - }).collect(Collectors.toSet()); - } - - private static List> splitIntoBatches(@NonNull Set numbers, int batchSize) { - List numberList = new ArrayList<>(numbers); - List> batches = new LinkedList<>(); - - for (int i = 0; i < numberList.size(); i += batchSize) { - List batch = numberList.subList(i, Math.min(numberList.size(), i + batchSize)); - batches.add(new HashSet<>(batch)); - } - - return batches; - } - - private static Optional> executeAndMergeContactDiscoveryRequests(@NonNull SignalServiceAccountManager accountManager, @NonNull List>> futures) { - Set results = new HashSet<>(); - try { - for (Future> future : futures) { - results.addAll(future.get()); - } - } catch (InterruptedException e) { - Log.w(TAG, "Contact discovery batch was interrupted.", e); - accountManager.reportContactDiscoveryServiceUnexpectedError(buildErrorReason(e)); - return Optional.absent(); - } catch (ExecutionException e) { - if (isAttestationError(e.getCause())) { - Log.w(TAG, "Failed during attestation.", e); - accountManager.reportContactDiscoveryServiceAttestationError(buildErrorReason(e.getCause())); - return Optional.absent(); - } else if (e.getCause() instanceof PushNetworkException) { - Log.w(TAG, "Failed due to poor network.", e); - return Optional.absent(); - } else if (e.getCause() instanceof NonSuccessfulResponseCodeException) { - Log.w(TAG, "Failed due to non successful response code.", e); - return Optional.absent(); - } else { - Log.w(TAG, "Failed for an unknown reason.", e); - accountManager.reportContactDiscoveryServiceUnexpectedError(buildErrorReason(e.getCause())); - return Optional.absent(); - } - } - - return Optional.of(results); - } - - private static boolean isAttestationError(Throwable e) { - return e instanceof CertificateException || - e instanceof SignatureException || - e instanceof UnauthenticatedQuoteException || - e instanceof UnauthenticatedResponseException || - e instanceof Quote.InvalidQuoteFormatException; - } - - private static KeyStore getIasKeyStore(@NonNull Context context) { - try { - TrustStore contactTrustStore = new IasTrustStore(context); - - KeyStore keyStore = KeyStore.getInstance("BKS"); - keyStore.load(contactTrustStore.getKeyStoreInputStream(), contactTrustStore.getKeyStorePassword().toCharArray()); - - return keyStore; - } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - private static String buildErrorReason(@Nullable Throwable t) { - if (t == null) { - return "null"; - } - - String rawString = android.util.Log.getStackTraceString(t); - List lines = Arrays.asList(rawString.split("\\n")); - - String errorString; - - if (lines.size() > 1) { - errorString = t.getClass().getName() + "\n" + Util.join(lines.subList(1, lines.size()), "\n"); + @WorkerThread + public static RegisteredState refreshDirectoryFor(@NonNull Context context, @NonNull Recipient recipient, boolean notifyOfNewUsers) throws IOException { + if (FeatureFlags.UUIDS) { + // TODO [greyson] Create a DirectoryHelperV2 when appropriate. + return DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers); } else { - errorString = t.getClass().getName(); - } - - if (errorString.length() > 1000) { - return errorString.substring(0, 1000); - } else { - return errorString; + return DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers); } } - - private static class DirectoryResult { - - private final Set numbers; - private final List newlyActiveRecipients; - - DirectoryResult(@NonNull Set numbers) { - this(numbers, Collections.emptyList()); - } - - DirectoryResult(@NonNull Set numbers, @NonNull List newlyActiveRecipients) { - this.numbers = numbers; - this.newlyActiveRecipients = newlyActiveRecipients; - } - - Set getNumbers() { - return numbers; - } - - List getNewlyActiveRecipients() { - return newlyActiveRecipients; - } - } - - private static class AccountHolder { - - private final boolean fresh; - private final Account account; - - private AccountHolder(Account account, boolean fresh) { - this.fresh = fresh; - this.account = account; - } - - @SuppressWarnings("unused") - public boolean isFresh() { - return fresh; - } - - public Account getAccount() { - return account; - } - - } - } diff --git a/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelperV1.java b/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelperV1.java new file mode 100644 index 000000000..4a4313db7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/contacts/sync/DirectoryHelperV1.java @@ -0,0 +1,351 @@ +package org.thoughtcrime.securesms.contacts.sync; + +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.annotation.SuppressLint; +import android.content.ContentResolver; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.crypto.SessionUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.sms.IncomingJoinedMessage; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.push.ContactTokenDetails; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +class DirectoryHelperV1 { + + private static final String TAG = DirectoryHelperV1.class.getSimpleName(); + + @WorkerThread + static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers) throws IOException { + if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return; + if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) return; + + List newlyActiveUsers = refreshDirectory(context, ApplicationDependencies.getSignalServiceAccountManager()); + + if (TextSecurePreferences.isMultiDevice(context)) { + ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob()); + } + + if (notifyOfNewUsers) notifyNewUsers(context, newlyActiveUsers); + } + + @SuppressLint("CheckResult") + private static @NonNull List refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager) throws IOException { + if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) { + return Collections.emptyList(); + } + + if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) { + return Collections.emptyList(); + } + + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + Stream eligibleRecipientDatabaseContactNumbers = Stream.of(recipientDatabase.getAllPhoneNumbers()); + Stream eligibleSystemDatabaseContactNumbers = Stream.of(ContactAccessor.getInstance().getAllContactsWithNumbers(context)); + Set eligibleContactNumbers = Stream.concat(eligibleRecipientDatabaseContactNumbers, eligibleSystemDatabaseContactNumbers).collect(Collectors.toSet()); + + try { + Future legacyRequest = getLegacyDirectoryResult(context, accountManager, recipientDatabase, eligibleContactNumbers); + DirectoryResult legacyResult = legacyRequest.get(); + + return legacyResult.getNewlyActiveRecipients(); + } catch (InterruptedException e) { + throw new IOException("[Batch] Operation was interrupted.", e); + } catch (ExecutionException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + Log.e(TAG, "[Batch] Experienced an unexpected exception.", e); + throw new AssertionError(e); + } + } + } + + @WorkerThread + static RegisteredState refreshDirectoryFor(@NonNull Context context, @NonNull Recipient recipient, boolean notifyOfNewUsers) throws IOException { + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); + Future legacyRequest = getLegacyRegisteredState(context, accountManager, recipientDatabase, recipient); + + try { + return legacyRequest.get(); + } catch (InterruptedException e) { + throw new IOException("[Singular] Operation was interrupted.", e); + } catch (ExecutionException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + Log.e(TAG, "[Singular] Experienced an unexpected exception.", e); + throw new AssertionError(e); + } + } + } + + 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).filter(Recipient::hasE164).map(Recipient::requireE164).toList(); + + DatabaseFactory.getContactsDatabase(context).removeDeletedRawContacts(account.get().getAccount()); + DatabaseFactory.getContactsDatabase(context).setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing); + + Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context); + RecipientDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientDatabase(context).resetAllSystemContactInfo(); + + try { + while (cursor != null && cursor.moveToNext()) { + String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)); + + if (!TextUtils.isEmpty(number)) { + 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)); + int phoneType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)); + Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)), + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); + + + handle.setSystemContactInfo(recipientId, displayName, contactPhotoUri, contactLabel, phoneType, contactUri.toString()); + } + } + } finally { + handle.finish(); + } + + if (NotificationChannels.supported()) { + try (RecipientDatabase.RecipientReader recipients = DatabaseFactory.getRecipientDatabase(context).getRecipientsWithNotificationChannels()) { + Recipient recipient; + while ((recipient = recipients.getNext()) != null) { + NotificationChannels.updateContactChannelName(context, recipient); + } + } + } + } catch (RemoteException | OperationApplicationException e) { + Log.w(TAG, "Failed to update contacts.", e); + } + } + } + + private static void notifyNewUsers(@NonNull Context context, + @NonNull List newUsers) + { + if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return; + + for (RecipientId newUser: newUsers) { + Recipient recipient = Recipient.resolved(newUser); + if (!SessionUtil.hasSession(context, recipient.getId()) && !recipient.isLocalNumber()) { + IncomingJoinedMessage message = new IncomingJoinedMessage(newUser); + Optional insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message); + + if (insertResult.isPresent()) { + int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + if (hour >= 9 && hour < 23) { + MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), true); + } else { + MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), false); + } + } + } + } + } + + private static Optional getOrCreateAccount(Context context) { + AccountManager accountManager = AccountManager.get(context); + Account[] accounts = accountManager.getAccountsByType("org.thoughtcrime.securesms"); + + Optional account; + + if (accounts.length == 0) account = createAccount(context); + else account = Optional.of(new AccountHolder(accounts[0], false)); + + if (account.isPresent() && !ContentResolver.getSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY)) { + ContentResolver.setSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY, true); + } + + return account; + } + + private static Optional createAccount(Context context) { + AccountManager accountManager = AccountManager.get(context); + Account account = new Account(context.getString(R.string.app_name), "org.thoughtcrime.securesms"); + + if (accountManager.addAccountExplicitly(account, null, null)) { + Log.i(TAG, "Created new account..."); + ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); + return Optional.of(new AccountHolder(account, true)); + } else { + Log.w(TAG, "Failed to create account!"); + return Optional.absent(); + } + } + + private static Future getLegacyDirectoryResult(@NonNull Context context, + @NonNull SignalServiceAccountManager accountManager, + @NonNull RecipientDatabase recipientDatabase, + @NonNull Set eligibleContactNumbers) + { + return SignalExecutors.UNBOUNDED.submit(() -> { + List activeTokens = accountManager.getContacts(eligibleContactNumbers); + + if (activeTokens != null) { + List activeIds = new LinkedList<>(); + List inactiveIds = new LinkedList<>(); + + Set inactiveContactNumbers = new HashSet<>(eligibleContactNumbers); + + for (ContactTokenDetails activeToken : activeTokens) { + activeIds.add(recipientDatabase.getOrInsertFromE164(activeToken.getNumber())); + inactiveContactNumbers.remove(activeToken.getNumber()); + } + + for (String inactiveContactNumber : inactiveContactNumbers) { + inactiveIds.add(recipientDatabase.getOrInsertFromE164(inactiveContactNumber)); + } + + 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(activeIds, inactiveIds); + updateContactsDatabase(context, activeIds, true); + + Set activeContactNumbers = Stream.of(activeIds).map(Recipient::resolved).filter(Recipient::hasSmsAddress).map(Recipient::requireSmsAddress).collect(Collectors.toSet()); + + if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context)) { + return new DirectoryResult(activeContactNumbers, newlyActiveIds); + } else { + TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true); + return new DirectoryResult(activeContactNumbers); + } + } + return new DirectoryResult(Collections.emptySet(), Collections.emptyList()); + }); + } + + private static Future getLegacyRegisteredState(@NonNull Context context, + @NonNull SignalServiceAccountManager accountManager, + @NonNull RecipientDatabase recipientDatabase, + @NonNull Recipient recipient) + { + return SignalExecutors.UNBOUNDED.submit(() -> { + boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED; + boolean systemContact = recipient.isSystemContact(); + Optional details = recipient.hasE164() ? accountManager.getContact(recipient.requireE164()) : Optional.absent(); + + if (details.isPresent()) { + recipientDatabase.setRegistered(recipient.getId(), RegisteredState.REGISTERED); + + if (Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) { + updateContactsDatabase(context, Util.asList(recipient.getId()), false); + } + + if (!activeUser && TextSecurePreferences.isMultiDevice(context)) { + ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob()); + } + + if (!activeUser && systemContact && !TextSecurePreferences.getNeedsSqlCipherMigration(context)) { + notifyNewUsers(context, Collections.singletonList(recipient.getId())); + } + + return RegisteredState.REGISTERED; + } else { + recipientDatabase.setRegistered(recipient.getId(), RegisteredState.NOT_REGISTERED); + return RegisteredState.NOT_REGISTERED; + } + }); + } + + + private static class DirectoryResult { + + private final Set numbers; + private final List newlyActiveRecipients; + + DirectoryResult(@NonNull Set numbers) { + this(numbers, Collections.emptyList()); + } + + DirectoryResult(@NonNull Set numbers, @NonNull List newlyActiveRecipients) { + this.numbers = numbers; + this.newlyActiveRecipients = newlyActiveRecipients; + } + + Set getNumbers() { + return numbers; + } + + List getNewlyActiveRecipients() { + return newlyActiveRecipients; + } + } + + private static class AccountHolder { + + private final boolean fresh; + private final Account account; + + private AccountHolder(Account account, boolean fresh) { + this.fresh = fresh; + this.account = account; + } + + @SuppressWarnings("unused") + public boolean isFresh() { + return fresh; + } + + public Account getAccount() { + return account; + } + + } + +} diff --git a/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java b/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java index 978949245..b0643e467 100644 --- a/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java +++ b/src/org/thoughtcrime/securesms/contactshare/ContactUtil.java @@ -25,7 +25,6 @@ import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress; 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; @@ -123,7 +122,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).requireAddress().toPhoneString(), locale); + values[i] = getPrettyPhoneNumber(choices.get(i).requireE164(), locale); } new AlertDialog.Builder(context) diff --git a/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java b/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java index d8c798e57..8e0452b12 100644 --- a/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/contactshare/SharedContactDetailsActivity.java @@ -228,7 +228,7 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct inviteButtonView.setOnClickListener(v -> { ContactUtil.selectRecipientThroughDialog(this, systemUsers, dynamicLanguage.getCurrentLocale(), recipient -> { - CommunicationActions.composeSmsThroughDefaultApp(this, recipient.requireAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); + CommunicationActions.composeSmsThroughDefaultApp(this, recipient, 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 db57dc89e..23701d81a 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -940,7 +940,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeText.appendInvite(inviteText); } else { Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.parse("smsto:" + recipient.get().requireAddress().serialize())); + intent.setData(Uri.parse("smsto:" + recipient.get().requireSmsAddress())); intent.putExtra("sms_body", inviteText); intent.putExtra(Intent.EXTRA_TEXT, inviteText); startActivity(intent); @@ -984,7 +984,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleAddShortcut() { - Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.get().requireAddress()); + Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.get().getId()); new AsyncTask() { @@ -1018,7 +1018,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity .or(Optional.fromNullable(recipient.get().getProfileName())) .or(recipient.get().toShortString()); - ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.get().requireAddress().serialize() + '-' + System.currentTimeMillis()) + ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.get().getId().serialize() + '-' + System.currentTimeMillis()) .setShortLabel(name) .setIcon(icon) .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getId())) @@ -1056,7 +1056,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity MessageSender.send(this, leaveMessage.get(), threadId, false, null); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this); - String groupId = groupRecipient.requireAddress().toGroupString(); + String groupId = groupRecipient.requireGroupId(); groupDatabase.setActive(groupId, false); groupDatabase.remove(groupId, Recipient.self().getId()); @@ -1072,7 +1072,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleEditPushGroup() { Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class); - intent.putExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA, recipient.get().requireAddress()); + intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId()); startActivityForResult(intent, GROUP_EDIT); } @@ -1116,7 +1116,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { try { Intent dialIntent = new Intent(Intent.ACTION_DIAL, - Uri.parse("tel:" + recipient.requireAddress().serialize())); + Uri.parse("tel:" + recipient.requireSmsAddress())); startActivity(dialIntent); } catch (ActivityNotFoundException anfe) { Log.w(TAG, anfe); @@ -1132,7 +1132,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleAddToContacts() { - if (recipient.get().requireAddress().isGroup()) return; + if (recipient.get().isGroup()) return; try { startActivityForResult(RecipientExporter.export(recipient.get()).asAddContactIntent(), ADD_CONTACT); @@ -1142,7 +1142,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private boolean handleDisplayQuickContact() { - if (recipient.get().requireAddress().isGroup()) return false; + if (recipient.get().isGroup()) return false; if (recipient.get().getContactUri() != null) { ContactsContract.QuickContact.showQuickContact(ConversationActivity.this, titleView, recipient.get().getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); @@ -1397,8 +1397,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (registeredState == RegisteredState.UNKNOWN) { try { - Log.i(TAG, "Refreshing directory for user: " + recipient.requireAddress().serialize()); - registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient); + Log.i(TAG, "Refreshing directory for user: " + recipient.getId().serialize()); + registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient, false); } catch (IOException e) { Log.w(TAG, e); } @@ -1487,13 +1487,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (params[0].isGroup()) { recipients.addAll(DatabaseFactory.getGroupDatabase(ConversationActivity.this) - .getGroupMembers(params[0].requireAddress().toGroupString(), false)); + .getGroupMembers(params[0].requireGroupId(), false)); } else { recipients.add(params[0]); } for (Recipient recipient : recipients) { - Log.i(TAG, "Loading identity for: " + recipient.requireAddress()); + Log.i(TAG, "Loading identity for: " + recipient.getId()); identityRecordList.add(identityDatabase.getIdentity(recipient.getId())); } @@ -2023,9 +2023,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @SuppressWarnings("SimplifiableIfStatement") private boolean isSelfConversation() { if (!TextSecurePreferences.isPushRegistered(this)) return false; - if (recipient.get().isGroup()) return false; + if (recipient.get().isGroup()) return false; - return Util.isOwnNumber(this, recipient.get().requireAddress()); + return recipient.get().isLocalNumber(); } private boolean isGroupConversation() { @@ -2152,8 +2152,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.isGroup() || - recipient.requireAddress().isEmail() || + recipient.isGroup() || + recipient.getEmail().isPresent() || inputPanel.getQuote().isPresent() || linkPreviewViewModel.hasLinkPreview() || needsSplit; @@ -2161,7 +2161,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "isManual Selection: " + sendButton.isManualSelection()); Log.i(TAG, "forceSms: " + forceSms); - if ((recipient.isMmsGroup() || recipient.requireAddress().isEmail()) && !isMmsEnabled) { + if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) { handleManualMmsRequired(); } else if (!forceSms && identityRecords.isUnverified()) { handleUnverifiedRecipients(); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index bdcada584..c88d06217 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1083,7 +1083,7 @@ public class ConversationFragment extends Fragment if (getContext() == null) return; ContactUtil.selectRecipientThroughDialog(getContext(), choices, locale, recipient -> { - CommunicationActions.composeSmsThroughDefaultApp(getContext(), recipient.requireAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); + CommunicationActions.composeSmsThroughDefaultApp(getContext(), recipient, getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))); }); } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java index cbe6f5542..a488e1774 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; public class ConversationTitleView extends RelativeLayout { @@ -116,7 +117,7 @@ public class ConversationTitleView extends RelativeLayout { this.title.setText(recipient.getName()); this.subtitle.setText(Stream.of(recipient.getParticipants()) - .filter(r -> !r.requireAddress().serialize().equals(localNumber)) + .filterNot(Recipient::isLocalNumber) .map(Recipient::toShortString) .collect(Collectors.joining(", "))); @@ -130,7 +131,7 @@ public class ConversationTitleView extends RelativeLayout { @SuppressLint("SetTextI18n") private void setNonContactRecipientTitle(Recipient recipient) { - this.title.setText(recipient.requireAddress().serialize()); + this.title.setText(Util.getFirstNonEmpty(recipient.getE164().orNull(), recipient.getUuid().orNull())); if (TextUtils.isEmpty(recipient.getProfileName())) { this.subtitle.setText(null); diff --git a/src/org/thoughtcrime/securesms/crypto/SessionUtil.java b/src/org/thoughtcrime/securesms/crypto/SessionUtil.java index e16b666dd..6ea0871cd 100644 --- a/src/org/thoughtcrime/securesms/crypto/SessionUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/SessionUtil.java @@ -4,16 +4,17 @@ import android.content.Context; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; -import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.signalservice.api.push.SignalServiceAddress; public class SessionUtil { - public static boolean hasSession(Context context, @NonNull Address address) { + public static boolean hasSession(@NonNull Context context, @NonNull RecipientId id) { SessionStore sessionStore = new TextSecureSessionStore(context); - SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(address.serialize(), SignalServiceAddress.DEFAULT_DEVICE_ID); + SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(Recipient.resolved(id).requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID); return sessionStore.containsSession(axolotlAddress); } diff --git a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java index fda485b9c..235384b83 100644 --- a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -48,7 +48,8 @@ public class UnidentifiedAccessUtil { try { byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); - byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context); + byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) + : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context) ; if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { ourUnidentifiedAccessKey = Util.getSecretBytes(16); @@ -56,7 +57,8 @@ public class UnidentifiedAccessUtil { Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) + " | Our access key present? " + (ourUnidentifiedAccessKey != null) + - " | Our certificate present? " + (ourUnidentifiedAccessCertificate != null)); + " | Our certificate present? " + (ourUnidentifiedAccessCertificate != null) + + " | UUID certificate supported? " + recipient.isUuidSupported()); if (theirUnidentifiedAccessKey != null && ourUnidentifiedAccessKey != null && @@ -83,7 +85,8 @@ public class UnidentifiedAccessUtil { try { byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); - byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context); + byte[] ourUnidentifiedAccessCertificate = Recipient.self().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) + : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context); if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { ourUnidentifiedAccessKey = Util.getSecretBytes(16); diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index edf308af2..4eb51a8d8 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -5,12 +5,10 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.SessionUtil; -import org.thoughtcrime.securesms.database.Address; 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; diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index 740dd0e8e..bffcd2df0 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; @@ -13,8 +13,10 @@ import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionStore; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.List; +import java.util.logging.Logger; public class TextSecureSessionStore implements SessionStore { @@ -54,12 +56,16 @@ public class TextSecureSessionStore implements SessionStore { @Override public boolean containsSession(SignalProtocolAddress address) { synchronized (FILE_LOCK) { - RecipientId recipientId = Recipient.external(context, address.getName()).getId(); - SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); + if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { + RecipientId recipientId = Recipient.external(context, address.getName()).getId(); + SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); - return sessionRecord != null && - sessionRecord.getSessionState().hasSenderChain() && - sessionRecord.getSessionState().getSessionVersion() == CiphertextMessage.CURRENT_VERSION; + return sessionRecord != null && + sessionRecord.getSessionState().hasSenderChain() && + sessionRecord.getSessionState().getSessionVersion() == CiphertextMessage.CURRENT_VERSION; + } else { + return false; + } } } @@ -95,7 +101,7 @@ public class TextSecureSessionStore implements SessionStore { for (SessionDatabase.SessionRow row : sessions) { if (row.getDeviceId() != address.getDeviceId()) { row.getRecord().archiveCurrentState(); - storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireAddress().serialize(), row.getDeviceId()), row.getRecord()); + storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireServiceId(), row.getDeviceId()), row.getRecord()); } } } @@ -107,7 +113,7 @@ public class TextSecureSessionStore implements SessionStore { for (SessionDatabase.SessionRow row : sessions) { row.getRecord().archiveCurrentState(); - storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireAddress().serialize(), row.getDeviceId()), row.getRecord()); + storeSession(new SignalProtocolAddress(Recipient.resolved(row.getRecipientId()).requireServiceId(), row.getDeviceId()), row.getRecord()); } } } diff --git a/src/org/thoughtcrime/securesms/database/Address.java b/src/org/thoughtcrime/securesms/database/Address.java deleted file mode 100644 index 59ae27b76..000000000 --- a/src/org/thoughtcrime/securesms/database/Address.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.thoughtcrime.securesms.database; - - -import android.os.Parcel; -import android.os.Parcelable; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.phonenumbers.NumberUtil; - -public class Address implements Parcelable, Comparable
{ - - public static final Parcelable.Creator
CREATOR = new Parcelable.Creator
() { - public Address createFromParcel(Parcel in) { - return new Address(in); - } - - public Address[] newArray(int size) { - return new Address[size]; - } - }; - - public static final Address UNKNOWN = new Address("Unknown"); - - private static final String TAG = Address.class.getSimpleName(); - - private final String address; - - private Address(@NonNull String address) { - if (address == null) throw new AssertionError(address); - this.address = address; - } - - public Address(Parcel in) { - this(in.readString()); - } - - public static @NonNull Address fromSerialized(@NonNull String serialized) { - return new Address(serialized); - } - - public boolean isGroup() { - return GroupUtil.isEncodedGroup(address); - } - - public boolean isMmsGroup() { - return GroupUtil.isMmsGroup(address); - } - - public boolean isEmail() { - return NumberUtil.isValidEmail(address); - } - - public boolean isPhone() { - return !isGroup() && !isEmail(); - } - - public @NonNull String toGroupString() { - if (!isGroup()) throw new AssertionError("Not group"); - return address; - } - - public @NonNull String toPhoneString() { - if (!isPhone()) { - if (isEmail()) throw new AssertionError("Not e164, is email"); - if (isGroup()) throw new AssertionError("Not e164, is group"); - throw new AssertionError("Not e164, unknown"); - } - return address; - } - - public @NonNull String toEmailString() { - if (!isEmail()) throw new AssertionError("Not email"); - return address; - } - - @Override - public @NonNull String toString() { - return address; - } - - public String serialize() { - return address; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || !(other instanceof Address)) return false; - return address.equals(((Address) other).address); - } - - @Override - public int hashCode() { - return address.hashCode(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(address); - } - - @Override - public int compareTo(@NonNull Address other) { - return address.compareTo(other.address); - } -} diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 2e20582ef..b83e8d775 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -1014,8 +1014,8 @@ public class MmsDatabase extends MessagingDatabase { long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), contentValues, insertListener); - if (message.getRecipient().requireAddress().isGroup()) { - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().requireAddress().toGroupString(), false); + if (message.getRecipient().isGroup()) { + List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().requireGroupId(), false); GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); receiptDatabase.insert(Stream.of(members).map(Recipient::getId).toList(), diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index 55472f311..2b15b357f 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -12,6 +12,8 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.util.Util; import java.io.IOException; @@ -23,7 +25,8 @@ public class PushDatabase extends Database { private static final String TABLE_NAME = "push"; public static final String ID = "_id"; public static final String TYPE = "type"; - public static final String SOURCE = "source"; + public static final String SOURCE_E164 = "source"; + public static final String SOURCE_UUID = "source_uuid"; public static final String DEVICE_ID = "device_id"; public static final String LEGACY_MSG = "body"; public static final String CONTENT = "content"; @@ -32,7 +35,7 @@ public class PushDatabase extends Database { public static final String SERVER_GUID = "server_guid"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER, " + + TYPE + " INTEGER, " + SOURCE_E164 + " TEXT, " + SOURCE_UUID + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER, " + SERVER_TIMESTAMP + " INTEGER DEFAULT 0, " + SERVER_GUID + " TEXT DEFAULT NULL);"; public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) { @@ -47,7 +50,8 @@ public class PushDatabase extends Database { } else { ContentValues values = new ContentValues(); values.put(TYPE, envelope.getType()); - values.put(SOURCE, envelope.getSource()); + values.put(SOURCE_UUID, envelope.getSourceUuid().orNull()); + values.put(SOURCE_E164, envelope.getSourceE164().orNull()); values.put(DEVICE_ID, envelope.getSourceDevice()); values.put(LEGACY_MSG, envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : ""); values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : ""); @@ -68,11 +72,14 @@ public class PushDatabase extends Database { null, null, null); if (cursor != null && cursor.moveToNext()) { - String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); - String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); + String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); + String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); + String uuid = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_UUID)); + String e164 = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_E164)); + SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(uuid), e164); return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), - cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)), + address, cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), Util.isEmpty(legacyMessage) ? null : Base64.decode(legacyMessage), @@ -105,27 +112,30 @@ public class PushDatabase extends Database { private Optional find(SignalServiceEnvelope envelope) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - Cursor cursor = null; + String query = TYPE + " = ? AND " + + DEVICE_ID + " = ? AND " + + LEGACY_MSG + " = ? AND " + + CONTENT + " = ? AND " + + TIMESTAMP + " = ? AND " + + "(" + + "(" + SOURCE_E164 + " NOT NULL AND " + SOURCE_E164 + " = ?) OR " + + "(" + SOURCE_UUID + " NOT NULL AND " + SOURCE_UUID + " = ?)" + + ")"; + String[] args = new String[] { String.valueOf(envelope.getType()), + String.valueOf(envelope.getSourceDevice()), + envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : "", + envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "", + String.valueOf(envelope.getTimestamp()), + String.valueOf(envelope.getSourceUuid().orNull()), + String.valueOf(envelope.getSourceE164().orNull()) }; - try { - cursor = database.query(TABLE_NAME, null, TYPE + " = ? AND " + SOURCE + " = ? AND " + - DEVICE_ID + " = ? AND " + LEGACY_MSG + " = ? AND " + - CONTENT + " = ? AND " + TIMESTAMP + " = ?" , - new String[] {String.valueOf(envelope.getType()), - envelope.getSource(), - String.valueOf(envelope.getSourceDevice()), - envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : "", - envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "", - String.valueOf(envelope.getTimestamp())}, - null, null, null); + try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { return Optional.of(cursor.getLong(cursor.getColumnIndexOrThrow(ID))); } else { return Optional.absent(); } - } finally { - if (cursor != null) cursor.close(); } } @@ -142,7 +152,8 @@ public class PushDatabase extends Database { return null; int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)); - String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)); + String sourceUuid = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_UUID)); + String sourceE164 = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE_E164)); int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)); String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); @@ -150,10 +161,14 @@ public class PushDatabase extends Database { long serverTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)); String serverGuid = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)); - return new SignalServiceEnvelope(type, source, deviceId, timestamp, + return new SignalServiceEnvelope(type, + new SignalServiceAddress(UuidUtil.parseOrNull(sourceUuid), sourceE164), + deviceId, + timestamp, legacyMessage != null ? Base64.decode(legacyMessage) : null, content != null ? Base64.decode(content) : null, - serverTimestamp, serverGuid); + serverTimestamp, + serverGuid); } catch (IOException e) { throw new AssertionError(e); } diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java index 499eec5c5..e2919ea78 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -21,15 +21,18 @@ 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; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.Closeable; import java.io.IOException; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; public class RecipientDatabase extends Database { @@ -64,6 +67,7 @@ public class RecipientDatabase extends Database { 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 UUID_SUPPORTED = "uuid_supported"; private static final String SORT_NAME = "sort_name"; @@ -73,23 +77,14 @@ public class RecipientDatabase extends Database { PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI, SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, - FORCE_SMS_SELECTION, + FORCE_SMS_SELECTION, UUID_SUPPORTED }; - private static final String[] ID_PROJECTION = new String[] { ID }; - - public static final String[] SEARCH_PROJECTION = new String[] { ID, SYSTEM_DISPLAY_NAME, SIGNAL_PROFILE_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "IFNULL(" + SYSTEM_DISPLAY_NAME + ", " + SIGNAL_PROFILE_NAME + ") AS " + SORT_NAME }; - - 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(); + private static final String[] ID_PROJECTION = new String[]{ID }; + public static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, SIGNAL_PROFILE_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "IFNULL(" + SYSTEM_DISPLAY_NAME + ", " + SIGNAL_PROFILE_NAME + ") AS " + SORT_NAME}; + static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) + .map(columnName -> TABLE_NAME + "." + columnName) + .toList(); public enum VibrateState { DEFAULT(0), ENABLED(1), DISABLED(2); @@ -173,12 +168,23 @@ public class RecipientDatabase extends Database { SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " + PROFILE_SHARING + " INTEGER DEFAULT 0, " + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + - FORCE_SMS_SELECTION + " INTEGER DEFAULT 0);"; + FORCE_SMS_SELECTION + " INTEGER DEFAULT 0, " + + UUID_SUPPORTED + " INTEGER DEFAULT 0);"; public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } + public @NonNull boolean containsPhoneOrUuid(@NonNull String id) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String query = UUID + " = ? OR " + PHONE + " = ?"; + String[] args = new String[]{id, id}; + + try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID }, query, args, null, null, null)) { + return cursor != null && cursor.moveToFirst(); + } + } + public @NonNull Optional getByE164(@NonNull String e164) { return getByColumn(PHONE, e164); } @@ -189,6 +195,15 @@ public class RecipientDatabase extends Database { public @NonNull Optional getByGroupId(@NonNull String groupId) { return getByColumn(GROUP_ID, groupId); + + } + + public @NonNull Optional getByUuid(@NonNull UUID uuid) { + return getByColumn(UUID, uuid.toString()); + } + + public @NonNull RecipientId getOrInsertFromUuid(@NonNull UUID uuid) { + return getOrInsertByColumn(UUID, uuid.toString()); } public @NonNull RecipientId getOrInsertFromE164(@NonNull String e164) { @@ -238,7 +253,10 @@ public class RecipientDatabase extends Database { @NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - Address address = addressFromCursor(cursor); + UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID))); + String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)); + String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL)); + String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)); boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1; String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE)); String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); @@ -261,6 +279,7 @@ public class RecipientDatabase extends Database { String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; + boolean uuidSupported = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_SUPPORTED)) == 1; MaterialColor color; byte[] profileKey = null; @@ -281,7 +300,7 @@ public class RecipientDatabase extends Database { } } - return new RecipientSettings(RecipientId.from(id), address, blocked, muteUntil, + return new RecipientSettings(RecipientId.from(id), uuid, e164, email, groupId, blocked, muteUntil, VibrateState.fromId(messageVibrateState), VibrateState.fromId(callVibrateState), Util.uri(messageRingtone), Util.uri(callRingtone), @@ -292,7 +311,7 @@ public class RecipientDatabase extends Database { systemPhoneLabel, systemContactUri, signalProfileName, signalProfileAvatar, profileSharing, notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), - forceSmsSelection); + forceSmsSelection, uuidSupported); } public BulkOperationsHandle resetAllSystemContactInfo() { @@ -394,6 +413,13 @@ public class RecipientDatabase extends Database { Recipient.live(id).refresh(); } + public void setUuidSupported(@NonNull RecipientId id, boolean supported) { + ContentValues values = new ContentValues(1); + values.put(UUID_SUPPORTED, supported ? "1" : "0"); + update(id, values); + Recipient.live(id).refresh(); + } + public void setProfileKey(@NonNull RecipientId id, @Nullable byte[] profileKey) { ContentValues values = new ContentValues(1); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); @@ -429,19 +455,84 @@ public class RecipientDatabase extends Database { Recipient.live(id).refresh(); } - public Set
getAllAddresses() { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Set
results = new HashSet<>(); + public void setPhoneNumber(@NonNull RecipientId id, @NonNull String e164) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(PHONE, e164); + update(id, contentValues); + Recipient.live(id).refresh(); + } - try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID, UUID, PHONE, EMAIL, GROUP_ID }, null, null, null, null, null)) { + public Set getAllPhoneNumbers() { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + Set results = new HashSet<>(); + + try (Cursor cursor = db.query(TABLE_NAME, new String[] { PHONE }, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - results.add(addressFromCursor(cursor)); + String number = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)); + + if (!TextUtils.isEmpty(number)) { + results.add(number); + } } } return results; } + public void markRegistered(@NonNull RecipientId id, @NonNull UUID uuid) { + ContentValues contentValues = new ContentValues(2); + contentValues.put(REGISTERED, RegisteredState.REGISTERED.getId()); + contentValues.put(UUID, uuid.toString().toLowerCase()); + update(id, contentValues); + Recipient.live(id).refresh(); + } + + /** + * Marks the user as registered without providing a UUID. This should only be used when one + * cannot be reasonably obtained. {@link #markRegistered(RecipientId, UUID)} should be strongly + * preferred. + */ + public void markRegistered(@NonNull RecipientId id) { + ContentValues contentValues = new ContentValues(2); + contentValues.put(REGISTERED, RegisteredState.REGISTERED.getId()); + update(id, contentValues); + Recipient.live(id).refresh(); + } + + public void markUnregistered(@NonNull RecipientId id) { + ContentValues contentValues = new ContentValues(2); + contentValues.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId()); + contentValues.put(UUID, (String) null); + update(id, contentValues); + Recipient.live(id).refresh(); + } + + public void bulkUpdatedRegisteredStatus(@NonNull Map registered, Collection unregistered) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.beginTransaction(); + + try { + for (Map.Entry entry : registered.entrySet()) { + ContentValues values = new ContentValues(2); + values.put(REGISTERED, RegisteredState.REGISTERED.getId()); + values.put(UUID, entry.getValue().toLowerCase()); + db.update(TABLE_NAME, values, ID_WHERE, new String[] { entry.getKey().serialize() }); + } + + for (RecipientId id : unregistered) { + ContentValues values = new ContentValues(1); + values.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId()); + values.put(UUID, (String) null); + db.update(TABLE_NAME, values, ID_WHERE, new String[] { id.serialize() }); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Deprecated public void setRegistered(@NonNull RecipientId id, RegisteredState registeredState) { ContentValues contentValues = new ContentValues(1); contentValues.put(REGISTERED, registeredState.getId()); @@ -449,8 +540,9 @@ public class RecipientDatabase extends Database { Recipient.live(id).refresh(); } - public void setRegistered(@NonNull List activeIds, - @NonNull List inactiveIds) + @Deprecated + public void setRegistered(@NonNull Collection activeIds, + @NonNull Collection inactiveIds) { for (RecipientId activeId : activeIds) { ContentValues contentValues = new ContentValues(1); @@ -673,7 +765,10 @@ public class RecipientDatabase extends Database { public static class RecipientSettings { private final RecipientId id; - private final Address address; + private final UUID uuid; + private final String e164; + private final String email; + private final String groupId; private final boolean blocked; private final long muteUntil; private final VibrateState messageVibrateState; @@ -696,9 +791,14 @@ public class RecipientDatabase extends Database { private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; + private final boolean uuidSupported; RecipientSettings(@NonNull RecipientId id, - @NonNull Address address, boolean blocked, long muteUntil, + @Nullable UUID uuid, + @Nullable String e164, + @Nullable String email, + @Nullable String groupId, + boolean blocked, long muteUntil, @NonNull VibrateState messageVibrateState, @NonNull VibrateState callVibrateState, @Nullable Uri messageRingtone, @@ -718,10 +818,14 @@ public class RecipientDatabase extends Database { boolean profileSharing, @Nullable String notificationChannel, @NonNull UnidentifiedAccessMode unidentifiedAccessMode, - boolean forceSmsSelection) + boolean forceSmsSelection, + boolean uuidSupported) { this.id = id; - this.address = address; + this.uuid = uuid; + this.e164 = e164; + this.email = email; + this.groupId = groupId; this.blocked = blocked; this.muteUntil = muteUntil; this.messageVibrateState = messageVibrateState; @@ -744,14 +848,27 @@ public class RecipientDatabase extends Database { this.notificationChannel = notificationChannel; this.unidentifiedAccessMode = unidentifiedAccessMode; this.forceSmsSelection = forceSmsSelection; + this.uuidSupported = uuidSupported; } public RecipientId getId() { return id; } - public @NonNull Address getAddress() { - return address; + public @Nullable UUID getUuid() { + return uuid; + } + + public @Nullable String getE164() { + return e164; + } + + public @Nullable String getEmail() { + return email; + } + + public @Nullable String getGroupId() { + return groupId; } public @Nullable MaterialColor getColor() { @@ -841,6 +958,10 @@ public class RecipientDatabase extends Database { public boolean isForceSmsSelection() { return forceSmsSelection; } + + public boolean isUuidSupported() { + return uuidSupported; + } } public static class RecipientReader implements Closeable { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 3005ff010..4c7a6a47d 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -570,7 +570,7 @@ public class SmsDatabase extends MessagingDatabase { if (message.getGroupId() == null) { groupRecipient = null; } else { - RecipientId id = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(message.getGroupId().serialize()); + RecipientId id = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(message.getGroupId()); groupRecipient = Recipient.resolved(id); } diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index ced1ecad2..0ba794ab2 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -28,10 +28,8 @@ 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.List; diff --git a/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java index 4ff7baf80..119c6c148 100644 --- a/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java +++ b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java @@ -8,7 +8,6 @@ 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; diff --git a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java index c1a875d88..e9e4e0909 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java @@ -25,7 +25,6 @@ import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -39,8 +38,10 @@ import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; @@ -1269,10 +1270,10 @@ public class ClassicOpenHelper extends SQLiteOpenHelper { if (Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"))); + String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - if (address.isPhone() && !TextUtils.isEmpty(address.toPhoneString())) { - Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString())); + if (!TextUtils.isEmpty(address) && !GroupUtil.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) { + Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)); try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.LOOKUP_KEY, @@ -1288,7 +1289,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper { contentValues.put("system_phone_label", contactCursor.getString(4)); contentValues.put("system_contact_uri", ContactsContract.Contacts.getLookupUri(contactCursor.getLong(2), contactCursor.getString(1)).toString()); - db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address.toPhoneString()}); + db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}); } } } diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 76a4d67e0..d2c12e87a 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.io.File; import java.util.List; +import java.util.UUID; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -90,12 +91,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int ATTACHMENT_TRANSFORM_PROPERTIES = 32; private static final int ATTACHMENT_CLEAR_HASHES = 33; private static final int ATTACHMENT_CLEAR_HASHES_2 = 34; + private static final int UUIDS = 35; - private static final int DATABASE_VERSION = 34; + private static final int DATABASE_VERSION = 35; private static final String DATABASE_NAME = "signal.db"; private final Context context; - private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { @@ -552,15 +553,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { for (NotificationChannel oldChannel : channels) { notificationManager.deleteNotificationChannel(oldChannel.getId()); - int startIndex = "contact_".length(); - int endIndex = oldChannel.getId().lastIndexOf("_"); - String address = oldChannel.getId().substring(startIndex, endIndex); + int startIndex = "contact_".length(); + int endIndex = oldChannel.getId().lastIndexOf("_"); + String address = oldChannel.getId().substring(startIndex, endIndex); String recipientId; try (Cursor cursor = db.query("recipient", new String[] { "_id" }, "phone = ? OR email = ? OR group_id = ?", new String[] { address, address, address}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { - recipientId = cursor.getString(0); + recipientId = cursor.getString(cursor.getColumnIndexOrThrow("_id")); } else { Log.w(TAG, "Couldn't find recipient for address: " + address); continue; @@ -614,6 +615,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { Glide.get(context).clearDiskCache(); } + if (oldVersion < UUIDS) { + db.execSQL("ALTER TABLE recipient ADD COLUMN uuid_supported INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE push ADD COLUMN source_uuid TEXT DEFAULT NULL"); + } + 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 072583f63..829399bbe 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java @@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.logging.Log; import net.sqlcipher.database.SQLiteDatabase; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.util.Conversions; import org.whispersystems.libsignal.state.SessionRecord; @@ -41,7 +40,7 @@ class SessionStoreMigrationHelper { for (File sessionFile : sessionFiles) { try { String[] parts = sessionFile.getName().split("[.]"); - Address address = Address.fromSerialized(parts[0]); + String address = parts[0]; int deviceId; @@ -79,7 +78,7 @@ class SessionStoreMigrationHelper { ContentValues contentValues = new ContentValues(); - contentValues.put(SessionDatabase.RECIPIENT_ID, address.serialize()); + contentValues.put(SessionDatabase.RECIPIENT_ID, address); contentValues.put(SessionDatabase.DEVICE, deviceId); contentValues.put(SessionDatabase.RECORD, sessionRecord.serialize()); diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 50e17c805..67fe1fb1d 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -10,7 +10,6 @@ import androidx.loader.content.AsyncTaskLoader; 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.MediaDatabase; import org.thoughtcrime.securesms.recipients.Recipient; diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java index 9d914ef5b..2f04e1c6f 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java @@ -6,10 +6,8 @@ import android.database.MatrixCursor; import android.database.MergeCursor; import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AbstractCursorLoader; diff --git a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java index cfc1b37c7..108032a04 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ThreadMediaLoader.java @@ -5,7 +5,6 @@ import android.content.Context; import android.database.Cursor; 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; diff --git a/src/org/thoughtcrime/securesms/database/model/Quote.java b/src/org/thoughtcrime/securesms/database/model/Quote.java index 85c6b84c1..ada38f09e 100644 --- a/src/org/thoughtcrime/securesms/database/model/Quote.java +++ b/src/org/thoughtcrime/securesms/database/model/Quote.java @@ -4,9 +4,7 @@ package org.thoughtcrime.securesms.database.model; import androidx.annotation.NonNull; 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 { diff --git a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 912257eef..f227ae9af 100644 --- a/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/src/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -34,6 +34,8 @@ import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.websocket.ConnectivityListener; +import java.util.UUID; + /** * Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies. */ @@ -120,7 +122,12 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr } @Override - public String getUser() { + public UUID getUuid() { + return TextSecurePreferences.getLocalUuid(context); + } + + @Override + public String getE164() { return TextSecurePreferences.getLocalNumber(context); } diff --git a/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java b/src/org/thoughtcrime/securesms/events/WebRtcViewModel.java index d6a69cd06..433e82b59 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.requireAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]"; + return "[State: " + state + ", recipient: " + recipient.getId().serialize() + ", 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 421fc6e8b..c2d854438 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -6,7 +6,6 @@ import android.graphics.Bitmap; import androidx.annotation.NonNull; 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; diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 3f3023cc4..1bf9cb3f2 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -5,6 +5,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.annimon.stream.Stream; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.ApplicationContext; @@ -22,6 +23,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.Base64; @@ -32,6 +34,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Collections; import java.util.HashSet; @@ -90,8 +93,8 @@ public class GroupMessageProcessor { List members = new LinkedList<>(); if (group.getMembers().isPresent()) { - for (String member : group.getMembers().get()) { - members.add(Recipient.external(context, member).getId()); + for (SignalServiceAddress member : group.getMembers().get()) { + members.add(Recipient.externalPush(context, member).getId()); } } @@ -114,8 +117,8 @@ public class GroupMessageProcessor { Set recordMembers = new HashSet<>(groupRecord.getMembers()); Set messageMembers = new HashSet<>(); - for (String messageMember : group.getMembers().get()) { - messageMembers.add(Recipient.external(context, messageMember).getId()); + for (SignalServiceAddress messageMember : group.getMembers().get()) { + messageMembers.add(Recipient.externalPush(context, messageMember).getId()); } Set addedMembers = new HashSet<>(messageMembers); @@ -135,7 +138,13 @@ public class GroupMessageProcessor { builder.clearMembers(); for (RecipientId addedMember : addedMembers) { - builder.addMembers(Recipient.resolved(addedMember).requireAddress().serialize()); + Recipient recipient = Recipient.resolved(addedMember); + + if (recipient.getE164().isPresent()) { + builder.addMembersE164(recipient.getE164().get()); + } + + builder.addMembers(createMember(RecipientUtil.toSignalServiceAddress(context, recipient))); } } else { builder.clearMembers(); @@ -164,7 +173,7 @@ public class GroupMessageProcessor { @NonNull SignalServiceGroup group, @NonNull GroupRecord record) { - Recipient sender = Recipient.external(context, content.getSender()); + Recipient sender = Recipient.externalPush(context, content.getSender()); if (record.getMembers().contains(sender.getId())) { ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), group.getGroupId())); @@ -186,8 +195,8 @@ public class GroupMessageProcessor { GroupContext.Builder builder = createGroupContext(group); builder.setType(GroupContext.Type.QUIT); - if (members.contains(Recipient.external(context, content.getSender()).getId())) { - database.remove(id, Recipient.external(context, content.getSender()).getId()); + if (members.contains(Recipient.externalPush(context, content.getSender()).getId())) { + database.remove(id, Recipient.externalPush(context, content.getSender()).getId()); if (outgoing) database.setActive(id, false); return storeMessage(context, content, group, builder.build(), outgoing); @@ -223,7 +232,7 @@ public class GroupMessageProcessor { } else { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt()); + IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(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); @@ -258,10 +267,29 @@ public class GroupMessageProcessor { } if (group.getMembers().isPresent()) { - builder.addAllMembers(group.getMembers().get()); + builder.addAllMembersE164(Stream.of(group.getMembers().get()) + .filter(a -> a.getNumber().isPresent()) + .map(a -> a.getNumber().get()) + .toList()); + builder.addAllMembers(Stream.of(group.getMembers().get()) + .map(GroupMessageProcessor::createMember) + .toList()); } return builder; } + public static GroupContext.Member createMember(SignalServiceAddress address) { + GroupContext.Member.Builder member = GroupContext.Member.newBuilder(); + + if (address.getUuid().isPresent()) { + member = member.setUuid(address.getUuid().get().toString()); + } + + if (address.getNumber().isPresent()) { + member = member.setE164(address.getNumber().get()); + } + + return member.build(); + } } diff --git a/src/org/thoughtcrime/securesms/groups/V1GroupManager.java b/src/org/thoughtcrime/securesms/groups/V1GroupManager.java index 45d6f1c27..d52d11ec0 100644 --- a/src/org/thoughtcrime/securesms/groups/V1GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/V1GroupManager.java @@ -20,6 +20,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; @@ -48,7 +49,7 @@ final class V1GroupManager { final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); final Recipient groupRecipient = Recipient.resolved(groupRecipientId); - memberIds.add(Recipient.external(context, TextSecurePreferences.getLocalNumber(context)).getId()); + memberIds.add(Recipient.self().getId()); groupDatabase.create(groupId, name, new LinkedList<>(memberIds), null, null); if (!mms) { @@ -71,7 +72,7 @@ final class V1GroupManager { final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); - memberAddresses.add(Recipient.external(context, TextSecurePreferences.getLocalNumber(context)).getId()); + memberAddresses.add(Recipient.self().getId()); groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses)); groupDatabase.updateTitle(groupId, name); groupDatabase.updateAvatar(groupId, avatarBytes); @@ -97,16 +98,19 @@ final class V1GroupManager { RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient groupRecipient = Recipient.resolved(groupRecipientId); - List numbers = new LinkedList<>(); + List uuidMembers = new LinkedList<>(); + List e164Members = new LinkedList<>(); for (RecipientId member : members) { - numbers.add(Recipient.resolved(member).requireAddress().serialize()); + Recipient recipient = Recipient.resolved(member); + uuidMembers.add(GroupMessageProcessor.createMember(RecipientUtil.toSignalServiceAddress(context, recipient))); } GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() .setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupId))) .setType(GroupContext.Type.UPDATE) - .addAllMembers(numbers); + .addAllMembersE164(e164Members) + .addAllMembers(uuidMembers); if (groupName != null) groupContextBuilder.setName(groupName); GroupContext groupContext = groupContextBuilder.build(); diff --git a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java index 236f3045c..f57e29c29 100644 --- a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java +++ b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java @@ -69,7 +69,7 @@ public class DirectoryRefreshJob extends BaseJob { if (recipient == null) { DirectoryHelper.refreshDirectory(context, notifyOfNewUsers); } else { - DirectoryHelper.refreshDirectoryFor(context, recipient); + DirectoryHelper.refreshDirectoryFor(context, recipient, notifyOfNewUsers); } } diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 11a0bcc20..8180dbf46 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob; +import org.thoughtcrime.securesms.migrations.UuidMigrationJob; import java.util.Arrays; import java.util.HashMap; @@ -88,6 +89,7 @@ public final class JobManagerFactories { put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory()); + put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory()); // Dead jobs put("PushContentReceiveJob", new FailingJob.Factory()); diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index 7741556fe..19f69a461 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -4,7 +4,6 @@ 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; @@ -13,8 +12,6 @@ import com.google.android.mms.pdu_alt.RetrieveConf; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; -import org.thoughtcrime.securesms.blurhash.BlurHash; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; @@ -35,10 +32,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.DuplicateMessageException; -import org.whispersystems.libsignal.InvalidMessageException; -import org.whispersystems.libsignal.LegacyMessageException; -import org.whispersystems.libsignal.NoSessionException; import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java index ba4f9270a..cac800e51 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java @@ -14,7 +14,6 @@ import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.recipients.Recipient; diff --git a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 15c83b787..9baf11d03 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -24,7 +24,6 @@ import com.klinker.android.send_message.Utils; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; @@ -221,7 +220,6 @@ public final class MmsSendJob extends SendJob { { SendReq req = new SendReq(); String lineNumber = getMyNumber(context); - Address destination = message.getRecipient().requireAddress(); MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId()); List scaledAttachments = message.getAttachments(); @@ -231,18 +229,18 @@ public final class MmsSendJob extends SendJob { req.setFrom(new EncodedStringValue(TextSecurePreferences.getLocalNumber(context))); } - if (destination.isMmsGroup()) { - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(destination.toGroupString(), false); + if (message.getRecipient().isMmsGroup()) { + List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().requireGroupId(), false); for (Recipient member : members) { if (message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST) { - req.addBcc(new EncodedStringValue(member.requireAddress().serialize())); + req.addBcc(new EncodedStringValue(member.requireSmsAddress())); } else { - req.addTo(new EncodedStringValue(member.requireAddress().serialize())); + req.addTo(new EncodedStringValue(member.requireSmsAddress())); } } } else { - req.addTo(new EncodedStringValue(destination.serialize())); + req.addTo(new EncodedStringValue(message.getRecipient().requireSmsAddress())); } req.setDate(System.currentTimeMillis() / 1000); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java index ddb5b8f99..84126cf47 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -12,12 +12,14 @@ 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.RecipientUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -67,16 +69,16 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob { RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); try (RecipientReader reader = database.readerForBlocked(database.getBlocked())) { - List blockedIndividuals = new LinkedList<>(); - List blockedGroups = new LinkedList<>(); + List blockedIndividuals = new LinkedList<>(); + List blockedGroups = new LinkedList<>(); Recipient recipient; while ((recipient = reader.getNext()) != null) { if (recipient.isGroup()) { - blockedGroups.add(GroupUtil.getDecodedId(recipient.requireAddress().toGroupString())); + blockedGroups.add(GroupUtil.getDecodedId(recipient.requireGroupId())); } else { - blockedIndividuals.add(recipient.requireAddress().serialize()); + blockedIndividuals.add(RecipientUtil.toSignalServiceAddress(context, recipient)); } } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index b77292638..30c54e545 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.jobs; import android.Manifest; -import android.content.Context; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; @@ -14,7 +13,6 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -25,6 +23,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.util.guava.Optional; @@ -37,6 +36,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -129,7 +129,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { Optional identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord); - out.write(new DeviceContact(recipient.requireAddress().toPhoneString(), + out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient), Optional.fromNullable(recipient.getName()), getAvatar(recipient.getContactUri()), Optional.fromNullable(recipient.getColor().serialize()), @@ -189,12 +189,12 @@ public class MultiDeviceContactUpdateJob extends BaseJob { boolean blocked = recipient.isBlocked(); Optional expireTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); - out.write(new DeviceContact(recipient.requireAddress().toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); + out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient), name, getAvatar(contactUri), color, verified, profileKey, blocked, expireTimer)); } if (ProfileKeyUtil.hasProfileKey(context)) { Recipient self = Recipient.self(); - out.write(new DeviceContact(TextSecurePreferences.getLocalNumber(context), + out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, Recipient.self()), Optional.absent(), Optional.absent(), Optional.of(self.getColor().serialize()), Optional.absent(), Optional.of(ProfileKeyUtil.getProfileKey(context)), @@ -300,8 +300,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob { private Optional getVerifiedMessage(Recipient recipient, Optional identity) throws InvalidNumberException { if (!identity.isPresent()) return Optional.absent(); - String destination = recipient.requireAddress().toPhoneString(); - IdentityKey identityKey = identity.get().getIdentityKey(); + SignalServiceAddress destination = RecipientUtil.toSignalServiceAddress(context, recipient); + IdentityKey identityKey = identity.get().getIdentityKey(); VerifiedMessage.VerifiedState state; diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 1c744c00a..27a11882d 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -13,6 +13,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; @@ -23,6 +24,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStre import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.ByteArrayInputStream; @@ -82,10 +84,10 @@ public class MultiDeviceGroupUpdateJob extends BaseJob { while ((record = reader.getNext()) != null) { if (!record.isMms()) { - List members = new LinkedList<>(); + List members = new LinkedList<>(); for (RecipientId member : record.getMembers()) { - members.add(Recipient.resolved(member).requireAddress().serialize()); + members.add(RecipientUtil.toSignalServiceAddress(context, Recipient.resolved(member))); } RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(record.getId(), record.isMms())); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java index 7ac9ed171..4f26dfc78 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -11,6 +11,8 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -68,7 +70,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeviceContactsOutputStream out = new DeviceContactsOutputStream(baos); - out.write(new DeviceContact(TextSecurePreferences.getLocalNumber(context), + out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, Recipient.self()), Optional.absent(), Optional.absent(), Optional.absent(), diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index 246bcc2d5..c37cad988 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -15,12 +15,14 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -89,7 +91,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob { for (SerializableSyncMessageId messageId : messageIds) { Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId)); - readMessages.add(new ReadMessage(recipient.requireAddress().serialize(), messageId.timestamp)); + readMessages.add(new ReadMessage(RecipientUtil.toSignalServiceAddress(context, recipient), messageId.timestamp)); } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java index 1c7a4efae..74141a047 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java @@ -10,9 +10,9 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -22,6 +22,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -99,9 +100,9 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); 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); + SignalServiceAddress verifiedAddress = RecipientUtil.toSignalServiceAddress(context, recipient); + VerifiedMessage verifiedMessage = new VerifiedMessage(verifiedAddress, new IdentityKey(identityKey, 0), verifiedState, timestamp); messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage), UnidentifiedAccessUtil.getAccessFor(context, recipient)); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java index 1433665ea..4bc6297c7 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java @@ -13,12 +13,15 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -76,7 +79,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId)); - ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(recipient.requireAddress().serialize(), messageId.timestamp); + ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(RecipientUtil.toSignalServiceAddress(context, recipient), messageId.timestamp); messageSender.sendMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context)); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index c2cb8c669..b018154b8 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -14,7 +14,6 @@ import android.util.Pair; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; -import com.google.android.mms.APN; import org.signal.libsignal.metadata.InvalidMetadataMessageException; import org.signal.libsignal.metadata.InvalidMetadataVersionException; @@ -35,7 +34,6 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.thoughtcrime.securesms.attachments.TombstoneAttachment; import org.thoughtcrime.securesms.attachments.UriAttachment; -import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; @@ -226,7 +224,7 @@ public class PushDecryptJob extends BaseJob { try { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); - SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); + SignalServiceAddress localAddress = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context))); SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator()); SignalServiceContent content = cipher.decrypt(envelope); @@ -290,7 +288,7 @@ public class PushDecryptJob extends BaseJob { Log.w(TAG, "Got unrecognized message..."); } - resetRecipientToPush(Recipient.external(context, content.getSender())); + resetRecipientToPush(Recipient.externalPush(context, content.getSender())); if (envelope.isPreKeySignalMessage()) { ApplicationDependencies.getJobManager().add(new RefreshPreKeysJob()); @@ -301,8 +299,7 @@ public class PushDecryptJob extends BaseJob { } catch (ProtocolInvalidMessageException e) { Log.w(TAG, e); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); - } - catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) { + } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) { Log.w(TAG, e); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); } catch (StorageFailedException e) { @@ -340,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_RECIPIENT, Recipient.external(context, content.getSender()).getId()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp()); @@ -356,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_RECIPIENT, Recipient.external(context, content.getSender()).getId()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); context.startService(intent); @@ -370,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_RECIPIENT, Recipient.external(context, content.getSender()).getId()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(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()); @@ -390,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_RECIPIENT, Recipient.external(context, content.getSender()).getId()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId()); context.startService(intent); } @@ -402,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_RECIPIENT, Recipient.external(context, content.getSender()).getId()); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId()); context.startService(intent); } @@ -411,7 +408,7 @@ public class PushDecryptJob extends BaseJob { @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), "", Optional.absent(), 0, @@ -432,7 +429,7 @@ public class PushDecryptJob extends BaseJob { if (threadId != null) { SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(content.getSender()); + sessionStore.deleteAllSessions(content.getSender().getIdentifier()); SecurityEvent.broadcastSecurityUpdateEvent(context); MessageNotifier.updateNotification(context, threadId); @@ -450,7 +447,7 @@ public class PushDecryptJob extends BaseJob { if (!recipient.isGroup()) { SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(recipient.requireAddress().toPhoneString()); + sessionStore.deleteAllSessions(recipient.requireServiceId()); SecurityEvent.broadcastSecurityUpdateEvent(context); @@ -482,7 +479,7 @@ public class PushDecryptJob extends BaseJob { private void handleUnknownGroupMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceGroup group) { - ApplicationDependencies.getJobManager().add(new RequestGroupInfoJob(Recipient.external(context, content.getSender()).getId(), group.getGroupId())); + ApplicationDependencies.getJobManager().add(new RequestGroupInfoJob(Recipient.externalPush(context, content.getSender()).getId(), group.getGroupId())); } private void handleExpirationUpdate(@NonNull SignalServiceContent content, @@ -514,7 +511,7 @@ public class PushDecryptJob extends BaseJob { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); } } catch (MmsException e) { - throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); + throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice()); } } @@ -573,11 +570,7 @@ public class PushDecryptJob extends BaseJob { } if (message.getMessage().getProfileKey().isPresent()) { - Recipient recipient = null; - - 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)); - + Recipient recipient = getSyncMessageDestination(message); if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true); @@ -591,7 +584,7 @@ public class PushDecryptJob extends BaseJob { MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); } catch (MmsException e) { - throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); + throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice()); } } @@ -622,8 +615,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(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); + List> expiringText = DatabaseFactory.getSmsDatabase(context).setTimestampRead(new SyncMessageId(Recipient.externalPush(context, readMessage.getSender()).getId(), readMessage.getTimestamp()), envelopeTimestamp); + List> expiringMedia = DatabaseFactory.getMmsDatabase(context).setTimestampRead(new SyncMessageId(Recipient.externalPush(context, readMessage.getSender()).getId(), readMessage.getTimestamp()), envelopeTimestamp); for (Pair expiringMessage : expiringText) { ApplicationContext.getInstance(context) @@ -644,7 +637,7 @@ public class PushDecryptJob extends BaseJob { } private void handleSynchronizeViewOnceOpenMessage(@NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp) { - RecipientId author = Recipient.external(context, openMessage.getSender()).getId(); + RecipientId author = Recipient.externalPush(context, openMessage.getSender()).getId(); long timestamp = openMessage.getTimestamp(); MessageRecord record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(timestamp, author); @@ -674,7 +667,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(Recipient.external(context, content.getSender()).getId(), + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Recipient.externalPush(context, content.getSender()).getId(), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, message.isViewOnce(), @@ -707,7 +700,7 @@ public class PushDecryptJob extends BaseJob { database.setTransactionSuccessful(); } } catch (MmsException e) { - throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); + throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice()); } finally { database.endTransaction(); } @@ -778,12 +771,13 @@ public class PushDecryptJob extends BaseJob { try { long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); - if (recipients.requireAddress().isGroup()) { - updateGroupReceiptStatus(message, messageId, recipients.requireAddress().toGroupString()); + if (recipients.isGroup()) { + updateGroupReceiptStatus(message, messageId, recipients.requireGroupId()); + } else { + database.markUnidentified(messageId, message.isUnidentified(recipients.requireServiceId())); } database.markAsSent(messageId, true); - database.markUnidentified(messageId, message.isUnidentified(recipients.requireAddress().serialize())); List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId); List stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList(); @@ -838,12 +832,12 @@ public class PushDecryptJob extends BaseJob { return; } - updateGroupReceiptStatus(message, record.getId(), recipient.requireAddress().toGroupString()); + updateGroupReceiptStatus(message, record.getId(), recipient.requireGroupId()); } private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull String groupString) { GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); - List messageRecipients = Stream.of(message.getRecipients()).map(address -> Recipient.external(context, address)).toList(); + List messageRecipients = Stream.of(message.getRecipients()).map(address -> Recipient.externalPush(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)); @@ -858,7 +852,7 @@ public class PushDecryptJob extends BaseJob { } for (Recipient member : members) { - receiptDatabase.setUnidentified(member.getId(), messageId, message.isUnidentified(member.requireAddress().serialize())); + receiptDatabase.setUnidentified(member.getId(), messageId, message.isUnidentified(member.requireServiceId())); } } @@ -882,7 +876,7 @@ public class PushDecryptJob extends BaseJob { } else { notifyTypingStoppedFromIncomingMessage(recipient, content.getSender(), content.getSenderDevice()); - IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, content.getSender()).getId(), + IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), @@ -915,7 +909,7 @@ public class PushDecryptJob extends BaseJob { } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - boolean isGroup = recipient.requireAddress().isGroup(); + boolean isGroup = recipient.isGroup(); MessagingDatabase database; long messageId; @@ -927,13 +921,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.requireAddress().toGroupString()); + updateGroupReceiptStatus(message, messageId, recipient.requireGroupId()); } 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.requireAddress().serialize())); + database.markUnidentified(messageId, message.isUnidentified(recipient.requireServiceId())); } database.markAsSent(messageId, true); @@ -1025,7 +1019,7 @@ public class PushDecryptJob extends BaseJob { } } - private void handleInvalidMessage(@NonNull String sender, + private void handleInvalidMessage(@NonNull SignalServiceAddress sender, int senderDevice, @NonNull Optional group, long timestamp, @@ -1034,7 +1028,7 @@ public class PushDecryptJob extends BaseJob { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp, group); + Optional insertResult = insertPlaceholder(sender.getIdentifier(), senderDevice, timestamp, group); if (insertResult.isPresent()) { smsDatabase.markAsInvalidMessage(insertResult.get().getMessageId()); @@ -1082,7 +1076,7 @@ public class PushDecryptJob extends BaseJob { @NonNull SignalServiceDataMessage message) { RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); - Recipient recipient = Recipient.external(context, content.getSender()); + Recipient recipient = Recipient.externalPush(context, content.getSender()); if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { database.setProfileKey(recipient.getId(), message.getProfileKey().get()); @@ -1094,7 +1088,7 @@ public class PushDecryptJob extends BaseJob { private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { - ApplicationDependencies.getJobManager().add(new SendDeliveryReceiptJob(Recipient.external(context, content.getSender()).getId(), message.getTimestamp())); + ApplicationDependencies.getJobManager().add(new SendDeliveryReceiptJob(Recipient.externalPush(context, content.getSender()).getId(), message.getTimestamp())); } @SuppressLint("DefaultLocale") @@ -1104,7 +1098,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(Recipient.external(context, content.getSender()).getId(), timestamp), System.currentTimeMillis()); + .incrementDeliveryReceiptCount(new SyncMessageId(Recipient.externalPush(context, content.getSender()).getId(), timestamp), System.currentTimeMillis()); } } @@ -1117,7 +1111,7 @@ public class PushDecryptJob extends BaseJob { Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementReadReceiptCount(new SyncMessageId(Recipient.external(context, content.getSender()).getId(), timestamp), content.getTimestamp()); + .incrementReadReceiptCount(new SyncMessageId(Recipient.externalPush(context, content.getSender()).getId(), timestamp), content.getTimestamp()); } } } @@ -1129,7 +1123,7 @@ public class PushDecryptJob extends BaseJob { return; } - Recipient author = Recipient.external(context, content.getSender()); + Recipient author = Recipient.externalPush(context, content.getSender()); long threadId; @@ -1184,7 +1178,7 @@ public class PushDecryptJob extends BaseJob { return Optional.absent(); } - RecipientId author = Recipient.external(context, quote.get().getAuthor().getNumber()).getId(); + RecipientId author = Recipient.externalPush(context, quote.get().getAuthor()).getId(); MessageRecord message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quote.get().getId(), author); if (message != null) { @@ -1313,7 +1307,7 @@ public class PushDecryptJob extends BaseJob { if (message.getMessage().getGroupInfo().isPresent()) { return Recipient.external(context, GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)); } else { - return Recipient.external(context, message.getDestination().get()); + return Recipient.externalPush(context, message.getDestination().get()); } } @@ -1321,12 +1315,12 @@ public class PushDecryptJob extends BaseJob { if (message.getGroupInfo().isPresent()) { return Recipient.external(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)); } else { - return Recipient.external(context, content.getSender()); + return Recipient.externalPush(context, content.getSender()); } } - private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) { - Recipient author = Recipient.external(context, sender); + private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull SignalServiceAddress sender, int device) { + Recipient author = Recipient.externalPush(context, sender); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient); if (threadId > 0) { @@ -1341,7 +1335,7 @@ public class PushDecryptJob extends BaseJob { return true; } - Recipient sender = Recipient.external(context, content.getSender()); + Recipient sender = Recipient.externalPush(context, content.getSender()); if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index e29fd3577..d6962df8b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -11,7 +11,6 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; 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.GroupReceiptDatabase.GroupReceiptInfo; @@ -45,6 +44,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Qu import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.IOException; @@ -96,7 +96,7 @@ public class PushGroupSendJob extends PushSendJob { throw new AssertionError("Not a group!"); } - if (!DatabaseFactory.getGroupDatabase(context).isActive(group.requireAddress().toGroupString())) { + if (!DatabaseFactory.getGroupDatabase(context).isActive(group.requireGroupId())) { throw new MmsException("Inactive group!"); } @@ -152,12 +152,12 @@ public class PushGroupSendJob extends PushSendJob { 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); + else target = getGroupMessageRecipients(message.getRecipient().requireGroupId(), messageId); List results = deliver(message, target); - 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 networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Recipient.externalPush(context, result.getAddress()).getId())).toList(); + List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Recipient.externalPush(context, result.getAddress()).getId(), result.getIdentityFailure().getIdentityKey())).toList(); + Set successIds = Stream.of(results).filter(result -> result.getSuccess() != null).map(SendMessageResult::getAddress).map(a -> Recipient.externalPush(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(); @@ -181,7 +181,7 @@ public class PushGroupSendJob extends PushSendJob { } for (SendMessageResult success : successes) { - DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Recipient.external(context, success.getAddress().getNumber()).getId(), + DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Recipient.externalPush(context, success.getAddress()).getId(), messageId, success.getSuccess().isUnidentified()); } @@ -231,33 +231,36 @@ public class PushGroupSendJob extends PushSendJob { rotateSenderCertificateIfNecessary(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - String groupId = message.getRecipient().requireAddress().toGroupString(); + String groupId = message.getRecipient().requireGroupId(); 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(Recipient::resolved).map(Recipient::requireAddress).map(this::getPushAddress).toList(); + List addresses = Stream.of(destinations).map(Recipient::resolved).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 -> Recipient.external(context, address.getNumber())) + List> unidentifiedAccess = Stream.of(destinations) + .map(Recipient::resolved) .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)) .toList(); if (message.isGroup()) { - OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message; - GroupContext groupContext = groupMessage.getGroupContext(); - SignalServiceAttachment avatar = attachmentPointers.isEmpty() ? null : attachmentPointers.get(0); - SignalServiceGroup.Type type = groupMessage.isGroupQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE; - SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupContext.getName(), groupContext.getMembersList(), avatar); - SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder() - .withTimestamp(message.getSentTimeMillis()) - .withExpiration(message.getRecipient().getExpireMessages()) - .asGroupMessage(group) - .build(); + OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message; + GroupContext groupContext = groupMessage.getGroupContext(); + SignalServiceAttachment avatar = attachmentPointers.isEmpty() ? null : attachmentPointers.get(0); + SignalServiceGroup.Type type = groupMessage.isGroupQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE; + List members = Stream.of(groupContext.getMembersList()) + .map(m -> new SignalServiceAddress(UuidUtil.parseOrNull(m.getUuid()), m.getE164())) + .toList(); + SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupContext.getName(), members, avatar); + SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getSentTimeMillis()) + .withExpiration(message.getRecipient().getExpireMessages()) + .asGroupMessage(group) + .build(); return messageSender.sendMessage(addresses, unidentifiedAccess, isRecipientUpdate, groupDataMessage); } else { diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index 7551fad9d..138e95af9 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -14,6 +14,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -25,6 +26,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -92,10 +95,11 @@ public class PushGroupUpdateJob extends BaseJob { .build(); } - List members = new LinkedList<>(); + List members = new LinkedList<>(); for (RecipientId member : record.get().getMembers()) { - members.add(Recipient.resolved(member).requireAddress().serialize()); + Recipient recipient = Recipient.resolved(member); + members.add(RecipientUtil.toSignalServiceAddress(context, recipient)); } SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE) @@ -117,7 +121,7 @@ public class PushGroupUpdateJob extends BaseJob { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); Recipient recipient = Recipient.resolved(source); - messageSender.sendMessage(new SignalServiceAddress(recipient.requireAddress().serialize()), + messageSender.sendMessage(RecipientUtil.toSignalServiceAddress(context, recipient), UnidentifiedAccessUtil.getAccessFor(context, recipient), message); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index bb12f95ea..d5af6e1d3 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -10,7 +10,6 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -29,6 +28,7 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -67,7 +67,7 @@ public class PushMediaSendJob extends PushSendJob { @WorkerThread public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Recipient recipient) { try { - if (!recipient.requireAddress().isPhone()) { + if (!recipient.hasServiceIdentifier()) { throw new AssertionError(); } @@ -164,7 +164,7 @@ public class PushMediaSendJob extends PushSendJob { ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false)); } catch (UntrustedIdentityException uie) { warn(TAG, "Failure", uie); - database.addMismatchedIdentity(messageId, Recipient.external(context, uie.getE164Number()).getId(), uie.getIdentityKey()); + database.addMismatchedIdentity(messageId, Recipient.external(context, uie.getIdentifier()).getId(), uie.getIdentityKey()); database.markAsSentFailed(messageId); } } @@ -189,19 +189,11 @@ public class PushMediaSendJob extends PushSendJob { throw new UndeliverableMessageException("No destination address."); } - final Address destination = message.getRecipient().requireAddress(); - - if (!destination.isPhone()) { - if (destination.isEmail()) throw new UndeliverableMessageException("Not e164, is email"); - if (destination.isGroup()) throw new UndeliverableMessageException("Not e164, is group"); - throw new UndeliverableMessageException("Not e164, unknown"); - } - try { rotateSenderCertificateIfNecessary(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - SignalServiceAddress address = getPushAddress(destination); + SignalServiceAddress address = getPushAddress(message.getRecipient()); List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); List serviceAttachments = getAttachmentPointersFor(attachments); Optional profileKey = getProfileKey(message.getRecipient()); @@ -223,7 +215,7 @@ public class PushMediaSendJob extends PushSendJob { .asExpirationUpdate(message.isExpirationUpdate()) .build(); - if (address.getNumber().equals(TextSecurePreferences.getLocalNumber(context))) { + if (Util.equals(TextSecurePreferences.getLocalUuid(context), address.getUuid().orNull())) { Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, mediaMessage, syncAccess); diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index 5f689d052..6b2cd193c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -1,17 +1,6 @@ package org.thoughtcrime.securesms.jobs; -import android.annotation.SuppressLint; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; -import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; public abstract class PushReceivedJob extends BaseJob { diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index d5cbf9ee8..81ed72736 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.PartProgressEvent; @@ -33,6 +32,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; @@ -106,9 +106,8 @@ public abstract class PushSendJob extends SendJob { return Optional.of(ProfileKeyUtil.getProfileKey(context)); } - protected SignalServiceAddress getPushAddress(Address address) { - String relay = null; - return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay)); + protected SignalServiceAddress getPushAddress(@NonNull Recipient recipient) { + return RecipientUtil.toSignalServiceAddress(context, recipient); } protected List getAttachmentsFor(List parts) { @@ -256,7 +255,9 @@ public abstract class PushSendJob extends SendJob { } } - return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(Recipient.resolved(quoteAuthor).requireAddress().serialize()), quoteBody, quoteAttachments)); + Recipient quoteAuthorRecipient = Recipient.resolved(quoteAuthor); + SignalServiceAddress quoteAddress = RecipientUtil.toSignalServiceAddress(context, quoteAuthorRecipient); + return Optional.of(new SignalServiceDataMessage.Quote(quoteId, quoteAddress, quoteBody, quoteAttachments)); } protected Optional getStickerFor(OutgoingMediaMessage message) { @@ -329,13 +330,13 @@ public abstract class PushSendJob extends SendJob { } protected SignalServiceSyncMessage buildSelfSendSyncMessage(@NonNull Context context, @NonNull SignalServiceDataMessage message, Optional syncAccess) { - String localNumber = TextSecurePreferences.getLocalNumber(context); - SentTranscriptMessage transcript = new SentTranscriptMessage(localNumber, - message.getTimestamp(), - message, - message.getExpiresInSeconds(), - Collections.singletonMap(localNumber, syncAccess.isPresent()), - false); + SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalUuid(context), TextSecurePreferences.getLocalNumber(context)); + SentTranscriptMessage transcript = new SentTranscriptMessage(localAddress, + message.getTimestamp(), + message, + message.getExpiresInSeconds(), + Collections.singletonMap(localAddress, syncAccess.isPresent()), + false); return SignalServiceSyncMessage.forSentTranscript(transcript); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 6fe451260..8bececeb8 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessM 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.NoSuchMessageException; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -21,6 +20,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -122,7 +122,7 @@ public class PushTextSendJob extends PushSendJob { ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false)); } catch (UntrustedIdentityException e) { warn(TAG, "Failure", e); - database.addMismatchedIdentity(record.getId(), Recipient.external(context, e.getE164Number()).getId(), e.getIdentityKey()); + database.addMismatchedIdentity(record.getId(), Recipient.external(context, e.getIdentifier()).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().requireAddress()); + SignalServiceAddress address = getPushAddress(message.getIndividualRecipient()); Optional profileKey = getProfileKey(message.getIndividualRecipient()); Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, message.getIndividualRecipient()); @@ -168,7 +168,7 @@ public class PushTextSendJob extends PushSendJob { .asEndSessionMessage(message.isEndSession()) .build(); - if (address.getNumber().equals(TextSecurePreferences.getLocalNumber(context))) { + if (Util.equals(TextSecurePreferences.getLocalUuid(context), address.getUuid().orNull())) { Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess); diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java index e6907af58..988412ea3 100644 --- a/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java @@ -51,7 +51,8 @@ public class RefreshUnidentifiedDeliveryAbilityJob extends BaseJob { @Override public void onRun() throws Exception { byte[] profileKey = ProfileKeyUtil.getProfileKey(context); - SignalServiceProfile profile = retrieveProfile(TextSecurePreferences.getLocalNumber(context)); + SignalServiceAddress address = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context))); + SignalServiceProfile profile = retrieveProfile(address); boolean enabled = profile.getUnidentifiedAccess() != null && isValidVerifier(profileKey, profile.getUnidentifiedAccess()); @@ -68,19 +69,19 @@ public class RefreshUnidentifiedDeliveryAbilityJob extends BaseJob { return exception instanceof PushNetworkException; } - private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException { + private SignalServiceProfile retrieveProfile(@NonNull SignalServiceAddress address) throws IOException { SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe(); if (pipe != null) { try { - return pipe.getProfile(new SignalServiceAddress(number), Optional.absent()); + return pipe.getProfile(address, Optional.absent()); } catch (IOException e) { Log.w(TAG, e); } } - return receiver.retrieveProfile(new SignalServiceAddress(number), Optional.absent()); + return receiver.retrieveProfile(address, Optional.absent()); } private boolean isValidVerifier(@NonNull byte[] profileKey, @NonNull String verifier) { diff --git a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index 4ec355872..8fa70e4e0 100644 --- a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -3,14 +3,15 @@ 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.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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -79,7 +80,7 @@ public class RequestGroupInfoJob extends BaseJob { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); Recipient recipient = Recipient.resolved(source); - messageSender.sendMessage(new SignalServiceAddress(recipient.requireAddress().serialize()), + messageSender.sendMessage(RecipientUtil.toSignalServiceAddress(context, recipient), UnidentifiedAccessUtil.getAccessFor(context, recipient), message); } diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index f32b3fadd..168ac1ae7 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -85,7 +85,7 @@ public class RetrieveProfileAvatarJob extends BaseJob { } if (TextUtils.isEmpty(profileAvatar)) { - Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.requireAddress().serialize()); + Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.getId().serialize()); AvatarHelper.delete(context, recipient.getId()); database.setProfileAvatar(recipient.getId(), profileAvatar); return; @@ -107,7 +107,7 @@ public class RetrieveProfileAvatarJob extends BaseJob { decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getId())); } catch (PushNetworkException e) { if (e.getCause() instanceof NonSuccessfulResponseCodeException) { - Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.requireAddress().serialize()); + Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.getId().serialize()); AvatarHelper.delete(context, recipient.getId()); } else { throw e; diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 7a1fd4549..7f236ade3 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import android.text.TextUtils; import org.thoughtcrime.securesms.ApplicationContext; @@ -16,6 +18,7 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.IdentityUtil; @@ -86,21 +89,21 @@ public class RetrieveProfileJob extends BaseJob { public void onCanceled() {} private void handleIndividualRecipient(Recipient recipient) throws IOException { - if (recipient.requireAddress().isPhone()) handlePhoneNumberRecipient(recipient); - else Log.w(TAG, "Skipping fetching profile of non-phone recipient"); + if (recipient.hasServiceIdentifier()) handlePhoneNumberRecipient(recipient); + else Log.w(TAG, "Skipping fetching profile of non-Signal recipient"); } private void handlePhoneNumberRecipient(Recipient recipient) throws IOException { - String number = recipient.requireAddress().toPhoneString(); + SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient); Optional unidentifiedAccess = getUnidentifiedAccess(recipient); SignalServiceProfile profile; try { - profile = retrieveProfile(number, unidentifiedAccess); + profile = retrieveProfile(address, unidentifiedAccess); } catch (NonSuccessfulResponseCodeException e) { if (unidentifiedAccess.isPresent()) { - profile = retrieveProfile(number, Optional.absent()); + profile = retrieveProfile(address, Optional.absent()); } else { throw e; } @@ -109,18 +112,19 @@ public class RetrieveProfileJob extends BaseJob { setIdentityKey(recipient, profile.getIdentityKey()); setProfileName(recipient, profile.getName()); setProfileAvatar(recipient, profile.getAvatar()); + setProfileCapabilities(recipient, profile.getCapabilities()); setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess()); } private void handleGroupRecipient(Recipient group) throws IOException { - List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.requireAddress().toGroupString(), false); + List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.requireGroupId(), false); for (Recipient recipient : recipients) { handleIndividualRecipient(recipient); } } - private SignalServiceProfile retrieveProfile(@NonNull String number, Optional unidentifiedAccess) + private SignalServiceProfile retrieveProfile(@NonNull SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe(); @@ -130,14 +134,14 @@ public class RetrieveProfileJob extends BaseJob { if (pipe != null) { try { - return pipe.getProfile(new SignalServiceAddress(number), unidentifiedAccess); + return pipe.getProfile(address, unidentifiedAccess); } catch (IOException e) { Log.w(TAG, e); } } SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); - return receiver.retrieveProfile(new SignalServiceAddress(number), unidentifiedAccess); + return receiver.retrieveProfile(address, unidentifiedAccess); } private void setIdentityKey(Recipient recipient, String identityKeyValue) { @@ -157,7 +161,7 @@ public class RetrieveProfileJob extends BaseJob { return; } - IdentityUtil.saveIdentity(context, recipient.requireAddress().toPhoneString(), identityKey); + IdentityUtil.saveIdentity(context, recipient.requireServiceId(), identityKey); } catch (InvalidKeyException | IOException e) { Log.w(TAG, e); } @@ -225,6 +229,14 @@ public class RetrieveProfileJob extends BaseJob { } } + private void setProfileCapabilities(@NonNull Recipient recipient, @Nullable SignalServiceProfile.Capabilities capabilities) { + if (capabilities == null) { + return; + } + + DatabaseFactory.getRecipientDatabase(context).setUuidSupported(recipient.getId(), capabilities.isUuid()); + } + private Optional getUnidentifiedAccess(@NonNull Recipient recipient) { Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); diff --git a/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java b/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java index 5dc7ba4e3..a4cdc12fc 100644 --- a/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java @@ -52,10 +52,12 @@ public class RotateCertificateJob extends BaseJob { @Override public void onRun() throws IOException { synchronized (RotateCertificateJob.class) { - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - byte[] certificate = accountManager.getSenderCertificate(); + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); + byte[] certificate = accountManager.getSenderCertificate(); + byte[] legacyCertificate = accountManager.getSenderCertificateLegacy(); TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate); + TextSecurePreferences.setUnidentifiedAccessCertificateLegacy(context, legacyCertificate); } } diff --git a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index db792ed11..8b45c5ed4 100644 --- a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -5,7 +5,6 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; diff --git a/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java index 210016f1d..179c8afc7 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.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.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -12,6 +11,7 @@ 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.recipients.RecipientUtil; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; @@ -76,7 +76,7 @@ public class SendDeliveryReceiptJob extends BaseJob { public void onRun() throws IOException, UntrustedIdentityException { SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); Recipient recipient = Recipient.resolved(recipientId); - SignalServiceAddress remoteAddress = new SignalServiceAddress(recipient.requireAddress().serialize()); + SignalServiceAddress remoteAddress = RecipientUtil.toSignalServiceAddress(context, recipient); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, Collections.singletonList(messageId), timestamp); diff --git a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java index 0d916f486..aa0647e38 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java @@ -13,7 +13,9 @@ 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.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; @@ -87,7 +89,7 @@ public class SendReadReceiptJob extends BaseJob { Recipient recipient = Recipient.resolved(recipientId); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - SignalServiceAddress remoteAddress = new SignalServiceAddress(recipient.requireAddress().serialize()); + SignalServiceAddress remoteAddress = RecipientUtil.toSignalServiceAddress(context, recipient); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); messageSender.sendReceipt(remoteAddress, diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java index 2d500a607..9de5898bb 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java @@ -9,7 +9,6 @@ import androidx.annotation.NonNull; import android.telephony.PhoneNumberUtils; import android.telephony.SmsManager; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; @@ -118,7 +117,7 @@ public class SmsSendJob extends SendJob { throw new UndeliverableMessageException("Trying to send a secure SMS?"); } - String recipient = message.getIndividualRecipient().requireAddress().serialize(); + String recipient = message.getIndividualRecipient().requireSmsAddress(); // 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 diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index aa75383b7..fdfe78625 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; @@ -85,12 +86,14 @@ public class TypingSendJob extends BaseJob { Optional groupId = Optional.absent(); if (recipient.isGroup()) { - recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireAddress().toGroupString(), false); - groupId = Optional.of(GroupUtil.getDecodedId(recipient.requireAddress().toGroupString())); + recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), false); + groupId = Optional.of(GroupUtil.getDecodedId(recipient.requireGroupId())); } + recipients = Stream.of(recipients).map(Recipient::resolve).toList(); + SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - List addresses = Stream.of(recipients).map(r -> new SignalServiceAddress(r.requireAddress().serialize())).toList(); + List addresses = Stream.of(recipients).map(r -> RecipientUtil.toSignalServiceAddress(context, r)).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 a0a59e797..d07c0e31e 100644 --- a/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java +++ b/src/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java @@ -131,7 +131,7 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity { getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_your_message)); } else { Recipient recipient = message.get().getMessageRecord().getRecipient(); - String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.requireAddress().serialize()) ; + String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getE164().orNull(), recipient.getEmail().orNull()) ; getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name)); } diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 55083f3a2..f6a1945d4 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -704,9 +704,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } 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())); + String displayName = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getE164().orNull(), recipient.getEmail().orNull()); composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null); } diff --git a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 326778ace..7c319d1e2 100644 --- a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -38,7 +38,7 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 5; + public static final int CURRENT_VERSION = 6; private static final class Version { static final int LEGACY = 1; @@ -46,6 +46,7 @@ public class ApplicationMigrations { static final int RECIPIENT_SEARCH = 3; static final int RECIPIENT_CLEANUP = 4; static final int AVATAR_MIGRATION = 5; + static final int UUIDS = 6; } /** @@ -178,6 +179,10 @@ public class ApplicationMigrations { jobs.put(Version.AVATAR_MIGRATION, new AvatarMigrationJob()); } + if (lastSeenVersion < Version.UUIDS) { + jobs.put(Version.UUIDS, new UuidMigrationJob()); + } + return jobs; } diff --git a/src/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java b/src/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java new file mode 100644 index 000000000..0a40699cf --- /dev/null +++ b/src/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java @@ -0,0 +1,92 @@ +package org.thoughtcrime.securesms.migrations; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; +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.SignalServiceAccountManager; + +import java.io.IOException; +import java.util.UUID; + +/** + * Couple migrations steps need to happen after we move to UUIDS. + * - We need to get our own UUID. + * - We need to fetch the new UUID sealed sender cert. + * - We need to do a directory sync so we can guarantee that all active users have UUIDs. + */ +public class UuidMigrationJob extends MigrationJob { + + public static final String KEY = "UuidMigrationJob"; + + private static final String TAG = Log.tag(UuidMigrationJob.class); + + UuidMigrationJob() { + this(new Parameters.Builder().addConstraint(NetworkConstraint.KEY).build()); + } + + private UuidMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + boolean isUiBlocking() { + return false; + } + + @Override + void performMigration() throws Exception { + if (!TextSecurePreferences.isPushRegistered(context)) { + Log.w(TAG, "Not registered! Skipping migration, as it wouldn't do anything."); + } + + fetchOwnUuid(context); + rotateSealedSenderCerts(context); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return e instanceof IOException; + } + + private static void fetchOwnUuid(@NonNull Context context) throws IOException { + RecipientId self = Recipient.self().getId(); + UUID localUuid = ApplicationDependencies.getSignalServiceAccountManager().getOwnUuid(); + + DatabaseFactory.getRecipientDatabase(context).markRegistered(self, localUuid); + TextSecurePreferences.setLocalUuid(context, localUuid); + } + + private static void rotateSealedSenderCerts(@NonNull Context context) throws IOException { + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); + byte[] certificate = accountManager.getSenderCertificate(); + byte[] legacyCertificate = accountManager.getSenderCertificateLegacy(); + + TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate); + TextSecurePreferences.setUnidentifiedAccessCertificateLegacy(context, legacyCertificate); + } + + + public static class Factory implements Job.Factory { + @Override + public @NonNull UuidMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new UuidMigrationJob(parameters); + } + } +} diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 676f49dea..307d436ec 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -5,7 +5,6 @@ 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; diff --git a/src/org/thoughtcrime/securesms/mms/QuoteId.java b/src/org/thoughtcrime/securesms/mms/QuoteId.java index 9a45e0021..0ebaf47b5 100644 --- a/src/org/thoughtcrime/securesms/mms/QuoteId.java +++ b/src/org/thoughtcrime/securesms/mms/QuoteId.java @@ -7,7 +7,6 @@ import androidx.annotation.Nullable; import org.json.JSONException; 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; diff --git a/src/org/thoughtcrime/securesms/mms/QuoteModel.java b/src/org/thoughtcrime/securesms/mms/QuoteModel.java index 60acc9dee..2261cfdf3 100644 --- a/src/org/thoughtcrime/securesms/mms/QuoteModel.java +++ b/src/org/thoughtcrime/securesms/mms/QuoteModel.java @@ -5,7 +5,6 @@ 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; diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index 8d001dca3..8615f8676 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -25,7 +25,6 @@ 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; diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index b90e4e9a7..12e32420a 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -12,7 +12,6 @@ import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 4ea5deaa1..22faedb38 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -41,8 +41,8 @@ public class NotificationChannels { private static final String TAG = NotificationChannels.class.getSimpleName(); private static class Version { - static final int MESSAGES_CATEGORY = 2; - static final int CALLS_PRIORITY_BUMP = 3; + static final int MESSAGES_CATEGORY = 2; + static final int CALLS_PRIORITY_BUMP = 3; } private static final int VERSION = 3; @@ -147,7 +147,7 @@ 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.requireAddress().serialize()); + String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getSmsAddress().or("")); return createChannelFor(context, generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled); } @@ -384,7 +384,7 @@ public class NotificationChannels { } NotificationChannel channel = new NotificationChannel(recipient.getNotificationChannel(), - getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress().serialize()), + getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getSmsAddress().or("")), NotificationManager.IMPORTANCE_HIGH); channel.setGroup(CATEGORY_MESSAGES); notificationManager.createNotificationChannel(channel); diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 2aa792aad..d88433197 100644 --- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -17,6 +17,9 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.CheckBoxPreference; import androidx.preference.Preference; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.logging.Log; + import com.google.firebase.iid.FirebaseInstanceId; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; @@ -24,8 +27,6 @@ import org.thoughtcrime.securesms.LogSubmitActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactIdentityManager; -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; @@ -185,7 +186,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment { protected Integer doInBackground(Void... params) { try { Context context = getActivity(); - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); try { accountManager.setGcmId(Optional.absent()); diff --git a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java index 7677d3781..7cf7c4caa 100644 --- a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java +++ b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java @@ -16,7 +16,6 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -79,6 +78,6 @@ public class ProfilePreference extends Preference { profileNameView.setText(profileName); } - profileNumberView.setText(self.requireAddress().toPhoneString()); + profileNumberView.setText(self.requireE164()); } } diff --git a/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java b/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java index 9fcd7f732..e21dcb9a3 100644 --- a/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java +++ b/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java @@ -7,7 +7,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.recipients.RecipientId; import java.io.File; @@ -22,10 +21,10 @@ public class AvatarHelper { private static final String AVATAR_DIRECTORY = "avatars"; - public static InputStream getInputStreamFor(@NonNull Context context, @NonNull RecipientId address) + public static InputStream getInputStreamFor(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException { - return new FileInputStream(getAvatarFile(context, address)); + return new FileInputStream(getAvatarFile(context, recipientId)); } public static List getAvatarFiles(@NonNull Context context) { @@ -58,5 +57,4 @@ public class AvatarHelper { out.close(); } } - } diff --git a/src/org/thoughtcrime/securesms/push/AccountManagerFactory.java b/src/org/thoughtcrime/securesms/push/AccountManagerFactory.java index befaf1179..9953e1210 100644 --- a/src/org/thoughtcrime/securesms/push/AccountManagerFactory.java +++ b/src/org/thoughtcrime/securesms/push/AccountManagerFactory.java @@ -2,42 +2,62 @@ package org.thoughtcrime.securesms.push; import android.content.Context; import android.os.AsyncTask; + +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.logging.Log; import com.google.android.gms.security.ProviderInstaller; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import java.util.UUID; + public class AccountManagerFactory { private static final String TAG = AccountManagerFactory.class.getSimpleName(); - public static SignalServiceAccountManager createManager(Context context) { - return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(context), - TextSecurePreferences.getLocalNumber(context), - TextSecurePreferences.getPushServerPassword(context), - BuildConfig.USER_AGENT); - } - - public static SignalServiceAccountManager createManager(final Context context, String number, String password) { + public static @NonNull SignalServiceAccountManager createAuthenticated(@NonNull Context context, + @NonNull UUID uuid, + @NonNull String number, + @NonNull String password) + { if (new SignalServiceNetworkAccess(context).isCensored(number)) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - try { - ProviderInstaller.installIfNeeded(context); - } catch (Throwable t) { - Log.w(TAG, t); - } - return null; + SignalExecutors.BOUNDED.execute(() -> { + try { + ProviderInstaller.installIfNeeded(context); + } catch (Throwable t) { + Log.w(TAG, t); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }); } return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number), - number, password, BuildConfig.USER_AGENT); + uuid, number, password, BuildConfig.USER_AGENT); + } + + /** + * Should only be used during registration when you haven't yet been assigned a UUID. + */ + public static @NonNull SignalServiceAccountManager createUnauthenticated(@NonNull Context context, + @NonNull String number, + @NonNull String password) + { + if (new SignalServiceNetworkAccess(context).isCensored(number)) { + SignalExecutors.BOUNDED.execute(() -> { + try { + ProviderInstaller.installIfNeeded(context); + } catch (Throwable t) { + Log.w(TAG, t); + } + }); + } + + return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number), + null, number, password, BuildConfig.USER_AGENT); } } diff --git a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java index f876a6a91..cbb7bd0fe 100644 --- a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java +++ b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java @@ -3,10 +3,6 @@ package org.thoughtcrime.securesms.push; import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java index 571d9e0fe..d5a9eea9f 100644 --- a/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; 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.guava.Optional; @@ -169,15 +170,17 @@ public final class LiveRecipient { private @NonNull Recipient fetchRecipientFromDisk(RecipientId id) { RecipientSettings settings = recipientDatabase.getRecipientSettings(id); - RecipientDetails details = settings.getAddress().isGroup() ? getGroupRecipientDetails(settings) - : getIndividualRecipientDetails(settings); + RecipientDetails details = settings.getGroupId() != null ? 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)); + boolean isLocalNumber = (settings.getE164() != null && settings.getE164().equals(TextSecurePreferences.getLocalNumber(context))) || + (settings.getUuid() != null && settings.getUuid().equals(TextSecurePreferences.getLocalUuid(context))); + return new RecipientDetails(context, null, Optional.absent(), systemContact, isLocalNumber, settings, null); } @@ -190,7 +193,7 @@ public final class LiveRecipient { List members = Stream.of(groupRecord.get().getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchRecipientFromDisk).toList(); Optional avatarId = Optional.absent(); - if (!settings.getAddress().isMmsGroup() && title == null) { + if (settings.getGroupId() != null && GroupUtil.isMmsGroup(settings.getGroupId()) && title == null) { title = unnamedGroupName; } diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 510ed6505..2c409a73e 100644 --- a/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -2,11 +2,10 @@ package org.thoughtcrime.securesms.recipients; import android.annotation.SuppressLint; import android.content.Context; -import android.database.Cursor; +import android.text.TextUtils; import androidx.annotation.AnyThread; import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; import androidx.lifecycle.MutableLiveData; import com.annimon.stream.Stream; @@ -20,12 +19,13 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; +import org.whispersystems.libsignal.util.guava.Optional; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.UUID; public final class LiveRecipientCache { @@ -80,7 +80,18 @@ public final class LiveRecipientCache { @NonNull Recipient getSelf() { synchronized (this) { if (localRecipientId == null) { - localRecipientId = recipientDatabase.getOrInsertFromE164(TextSecurePreferences.getLocalNumber(context)); + UUID localUuid = TextSecurePreferences.getLocalUuid(context); + String localE164 = TextSecurePreferences.getLocalNumber(context); + + if (localUuid != null) { + localRecipientId = recipientDatabase.getByUuid(localUuid).or(recipientDatabase.getByE164(localE164)).orNull(); + } else { + localRecipientId = recipientDatabase.getByE164(localE164).orNull(); + } + + if (localRecipientId == null) { + throw new MissingRecipientError(localRecipientId); + } } } diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 949814e65..ae9be23f5 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -21,13 +21,14 @@ import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; 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.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.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; @@ -35,20 +36,28 @@ import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Preconditions; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.UUID; public class Recipient { public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, new RecipientDetails()); + private static final String TAG = Log.tag(Recipient.class); + private final RecipientId id; private final boolean resolving; - private final Address address; + private final UUID uuid; + private final String e164; + private final String email; + private final String groupId; private final List participants; private final Optional groupAvatarId; private final boolean localNumber; @@ -74,6 +83,7 @@ public class Recipient { private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; + private final boolean uuidSupported; /** @@ -98,22 +108,100 @@ public class Recipient { } /** - * 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. + * Returns a fully-populated {@link Recipient} based off of a {@link SignalServiceAddress}, + * creating one in the database if necessary. Convenience overload of + * {@link #externalPush(Context, UUID, String)} */ @WorkerThread - public static @NonNull Recipient external(@NonNull Context context, @NonNull String address) { - Preconditions.checkNotNull(address, "Address cannot be null."); + public static @NonNull Recipient externalPush(@NonNull Context context, @NonNull SignalServiceAddress signalServiceAddress) { + return externalPush(context, signalServiceAddress.getUuid().orNull(), signalServiceAddress.getNumber().orNull()); + } + + /** + * Returns a fully-populated {@link Recipient} based off of a UUID and phone number, creating one + * in the database if necessary. We want both piece of information so we're able to associate them + * both together, depending on which are available. + * + * In particular, while we'll eventually get the UUID of a user created via a phone number + * (through a directory sync), the only way we can store the phone number is by retrieving it from + * sent messages and whatnot. So we should store it when available. + */ + @WorkerThread + public static @NonNull Recipient externalPush(@NonNull Context context, @Nullable UUID uuid, @Nullable String e164) { + RecipientDatabase db = DatabaseFactory.getRecipientDatabase(context); + Optional uuidUser = uuid != null ? db.getByUuid(uuid) : Optional.absent(); + Optional e164User = e164 != null ? db.getByE164(e164) : Optional.absent(); + + if (uuidUser.isPresent()) { + Recipient recipient = resolved(uuidUser.get()); + + if (e164 != null && !recipient.getE164().isPresent() && !e164User.isPresent()) { + db.setPhoneNumber(recipient.getId(), e164); + } + + return resolved(recipient.getId()); + } else if (e164User.isPresent()) { + Recipient recipient = resolved(e164User.get()); + + if (uuid != null && !recipient.getUuid().isPresent()) { + db.markRegistered(recipient.getId(), uuid); + } else if (!recipient.isRegistered()) { + db.markRegistered(recipient.getId()); + + Log.i(TAG, "No UUID! Scheduling a fetch."); + ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false)); + } + + return resolved(recipient.getId()); + } else if (uuid != null) { + RecipientId id = db.getOrInsertFromUuid(uuid); + db.markRegistered(id, uuid); + + if (e164 != null) { + db.setPhoneNumber(id, e164); + } + + return resolved(id); + } else if (e164 != null) { + Recipient recipient = resolved(db.getOrInsertFromE164(e164)); + + if (!recipient.isRegistered()) { + db.markRegistered(recipient.getId()); + + Log.i(TAG, "No UUID! Scheduling a fetch."); + ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false)); + } + + return resolved(recipient.getId()); + } else { + throw new AssertionError("You must provide either a UUID or phone number!"); + } + } + + /** + * Returns a fully-populated {@link Recipient} based off of a string identifier, creating one in + * the database if necessary. The identifier may be a uuid, phone number, email, + * or serialized groupId. + * + * If the identifier is a UUID of a Signal user, prefer using + * {@link #externalPush(Context, UUID, String)} or its overload, as this will let us associate + * the phone number with the recipient. + */ + @WorkerThread + public static @NonNull Recipient external(@NonNull Context context, @NonNull String identifier) { + Preconditions.checkNotNull(identifier, "Identifier cannot be null!"); RecipientDatabase db = DatabaseFactory.getRecipientDatabase(context); RecipientId id = null; - if (GroupUtil.isEncodedGroup(address)) { - id = db.getOrInsertFromGroupId(address); - } else if (NumberUtil.isValidEmail(address)) { - id = db.getOrInsertFromEmail(address); + if (UuidUtil.isUuid(identifier)) { + id = db.getOrInsertFromUuid(UuidUtil.parseOrThrow(identifier)); + } else if (GroupUtil.isEncodedGroup(identifier)) { + id = db.getOrInsertFromGroupId(identifier); + } else if (NumberUtil.isValidEmail(identifier)) { + id = db.getOrInsertFromEmail(identifier); } else { - String e164 = PhoneNumberFormatter.get(context).format(address); + String e164 = PhoneNumberFormatter.get(context).format(identifier); id = db.getOrInsertFromE164(e164); } @@ -127,7 +215,10 @@ public class Recipient { Recipient(@NonNull RecipientId id) { this.id = id; this.resolving = true; - this.address = null; + this.uuid = null; + this.e164 = null; + this.email = null; + this.groupId = null; this.participants = Collections.emptyList(); this.groupAvatarId = Optional.absent(); this.localNumber = false; @@ -153,12 +244,16 @@ public class Recipient { this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; this.forceSmsSelection = false; + this.uuidSupported = false; } Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details) { this.id = id; this.resolving = false; - this.address = details.address; + this.uuid = details.uuid; + this.e164 = details.e164; + this.email = details.email; + this.groupId = details.groupId; this.participants = details.participants; this.groupAvatarId = details.groupAvatarId; this.localNumber = details.isLocalNumber; @@ -184,6 +279,7 @@ public class Recipient { this.notificationChannel = details.notificationChannel; this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.forceSmsSelection = details.forceSmsSelection; + this.uuidSupported = details.uuidSuported; } public @NonNull RecipientId getId() { @@ -199,7 +295,7 @@ public class Recipient { } public @Nullable String getName() { - if (this.name == null && address != null && address.isMmsGroup()) { + if (this.name == null && groupId != null && GroupUtil.isMmsGroup(groupId)) { List names = new LinkedList<>(); for (Recipient recipient : participants) { @@ -223,7 +319,7 @@ public class Recipient { return profileName; } - return requireAddress().serialize(); + return getSmsAddress().or(""); } public @NonNull MaterialColor getColor() { @@ -233,11 +329,95 @@ public class Recipient { else return ContactColors.UNKNOWN_COLOR; } - public @NonNull Address requireAddress() { - if (resolving) { - return resolve().address; + public @NonNull Optional getUuid() { + return Optional.fromNullable(uuid); + } + + public @NonNull Optional getE164() { + return Optional.fromNullable(e164); + } + + public @NonNull Optional getEmail() { + return Optional.fromNullable(email); + } + + public @NonNull Optional getGroupId() { + return Optional.fromNullable(groupId); + } + + public @NonNull Optional getSmsAddress() { + return Optional.fromNullable(e164).or(Optional.fromNullable(email)); + } + + public @NonNull String requireE164() { + String resolved = resolving ? resolve().e164 : e164; + + if (resolved == null) { + throw new MissingAddressError(); + } + + return resolved; + } + + public @NonNull String requireEmail() { + String resolved = resolving ? resolve().email : email; + + if (resolved == null) { + throw new MissingAddressError(); + } + + return resolved; + } + + public @NonNull String requireSmsAddress() { + Recipient recipient = resolving ? resolve() : this; + + if (recipient.getE164().isPresent()) { + return recipient.getE164().get(); + } else if (recipient.getEmail().isPresent()) { + return recipient.getEmail().get(); } else { - return address; + throw new MissingAddressError(); + } + } + + public boolean hasSmsAddress() { + return getE164().or(getEmail()).isPresent(); + } + + public boolean hasE164() { + return getE164().isPresent(); + } + + public boolean hasUuid() { + return getUuid().isPresent(); + } + + public @NonNull String requireGroupId() { + String resolved = resolving ? resolve().groupId : groupId; + + if (resolved == null) { + throw new MissingAddressError(); + } + + return resolved; + } + + public boolean hasServiceIdentifier() { + return uuid != null || e164 != null; + } + + /** + * @return A string identifier able to be used with the Signal service. Prefers UUID, and if not + * available, will return an E164 number. + */ + public @NonNull String requireServiceId() { + Recipient resolved = resolving ? resolve() : this; + + if (resolved.getUuid().isPresent()) { + return resolved.getUuid().get().toString(); + } else { + return getE164().get(); } } @@ -245,6 +425,23 @@ public class Recipient { return customLabel; } + /** + * @return A single string to represent the recipient, in order of precedence: + * + * Group ID > UUID > Phone > Email + */ + public @NonNull String requireStringId() { + Recipient resolved = resolving ? resolve() : this; + + if (resolved.isGroup()) { + return resolved.requireGroupId(); + } else if (resolved.getUuid().isPresent()) { + return resolved.getUuid().get().toString(); + } + + return requireSmsAddress(); + } + public Optional getDefaultSubscriptionId() { return defaultSubscriptionId; } @@ -262,20 +459,21 @@ public class Recipient { } public boolean isGroup() { - return requireAddress().isGroup(); + return resolve().groupId != null; } private boolean isGroupInternal() { - return address != null && address.isGroup(); + return groupId != null; } public boolean isMmsGroup() { - return requireAddress().isMmsGroup(); + String groupId = resolve().groupId; + return groupId != null && GroupUtil.isMmsGroup(groupId); } public boolean isPushGroup() { - Address address = requireAddress(); - return address.isGroup() && !address.isMmsGroup(); + String groupId = resolve().groupId; + return groupId != null && !GroupUtil.isMmsGroup(groupId); } public @NonNull List getParticipants() { @@ -283,7 +481,7 @@ public class Recipient { } public @NonNull String toShortString() { - return Optional.fromNullable(getName()).or(Optional.fromNullable(address != null ? address.serialize() : null)).or(""); + return Optional.fromNullable(getName()).or(getE164()).or(getEmail()).or(""); } public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { @@ -305,8 +503,8 @@ public class Recipient { public @Nullable ContactPhoto getContactPhoto() { if (localNumber) return null; - else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(address, groupAvatarId.get()); - else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); + else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(groupId, groupAvatarId.get()); + else if (systemContactPhoto != null) return new SystemContactPhoto(id, systemContactPhoto, 0); else if (profileAvatar != null) return new ProfileContactPhoto(id, profileAvatar); else return null; } @@ -370,6 +568,13 @@ public class Recipient { return forceSmsSelection; } + /** + * @return True if this recipient can support receiving UUID-only messages, otherwise false. + */ + public boolean isUuidSupported() { + return FeatureFlags.UUIDS && uuidSupported; + } + public @Nullable byte[] getProfileKey() { return profileKey; } @@ -410,4 +615,7 @@ public class Recipient { public int hashCode() { return Objects.hash(id); } + + private static class MissingAddressError extends AssertionError { + } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 189066283..afd9ac7a9 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -7,7 +7,6 @@ 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; @@ -18,10 +17,14 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.util.LinkedList; import java.util.List; +import java.util.UUID; public class RecipientDetails { - final Address address; + final UUID uuid; + final String e164; + final String email; + final String groupId; final String name; final String customLabel; final Uri systemContactPhoto; @@ -48,6 +51,7 @@ public class RecipientDetails { final String notificationChannel; final UnidentifiedAccessMode unidentifiedAccessMode; final boolean forceSmsSelection; + final boolean uuidSuported; RecipientDetails(@NonNull Context context, @Nullable String name, @@ -61,7 +65,10 @@ public class RecipientDetails { this.systemContactPhoto = Util.uri(settings.getSystemContactPhotoUri()); this.customLabel = settings.getSystemPhoneLabel(); this.contactUri = Util.uri(settings.getSystemContactUri()); - this.address = settings.getAddress(); + this.uuid = settings.getUuid(); + this.e164 = settings.getE164(); + this.email = settings.getEmail(); + this.groupId = settings.getGroupId(); this.color = settings.getColor(); this.messageRingtone = settings.getMessageRingtone(); this.callRingtone = settings.getCallRingtone(); @@ -83,6 +90,7 @@ public class RecipientDetails { this.notificationChannel = settings.getNotificationChannel(); this.unidentifiedAccessMode = settings.getUnidentifiedAccessMode(); this.forceSmsSelection = settings.isForceSmsSelection(); + this.uuidSuported = settings.isUuidSupported(); if (name == null) this.name = settings.getSystemDisplayName(); else this.name = name; @@ -93,7 +101,10 @@ public class RecipientDetails { this.systemContactPhoto = null; this.customLabel = null; this.contactUri = null; - this.address = Address.UNKNOWN; + this.uuid = null; + this.e164 = null; + this.email = null; + this.groupId = null; this.color = null; this.messageRingtone = null; this.callRingtone = null; @@ -116,5 +127,6 @@ public class RecipientDetails { this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; this.forceSmsSelection = false; this.name = null; + this.uuidSuported = false; } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java index d9b18e7c2..8f053c593 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java @@ -4,8 +4,6 @@ import android.content.Intent; import android.provider.ContactsContract; import android.text.TextUtils; -import org.thoughtcrime.securesms.database.Address; - import static android.content.Intent.ACTION_INSERT_OR_EDIT; public final class RecipientExporter { @@ -24,7 +22,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.requireAddress()); + addAddressToIntent(intent, recipient); return intent; } @@ -34,13 +32,11 @@ public final class RecipientExporter { } } - private static void addAddressToIntent(Intent intent, Address address) { - if (address.isPhone()) { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, address.toPhoneString()); - } else if (address.isEmail()) { - intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address.toEmailString()); - } else { - throw new RuntimeException("Cannot export Recipient with neither phone nor email"); + private static void addAddressToIntent(Intent intent, Recipient recipient) { + if (recipient.getE164().isPresent()) { + intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.requireE164()); + } else if (recipient.getEmail().isPresent()) { + intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.requireEmail()); } } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/src/org/thoughtcrime/securesms/recipients/RecipientUtil.java new file mode 100644 index 000000000..8769a38bf --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -0,0 +1,52 @@ +package org.thoughtcrime.securesms.recipients; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.util.FeatureFlags; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; + +import java.io.IOException; + +public class RecipientUtil { + + private static final String TAG = Log.tag(RecipientUtil.class); + + /** + * This method will do it's best to craft a fully-populated {@link SignalServiceAddress} based on + * the provided recipient. This includes performing a possible network request if no UUID is + * available. + */ + @WorkerThread + public static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient) { + recipient = recipient.resolve(); + + if (!recipient.getUuid().isPresent() && !recipient.getE164().isPresent()) { + throw new AssertionError(recipient.getId() + " - No UUID or phone number!"); + } + + if (FeatureFlags.UUIDS && !recipient.getUuid().isPresent()) { + Log.i(TAG, recipient.getId() + " is missing a UUID..."); + try { + RegisteredState state = DirectoryHelper.refreshDirectoryFor(context, recipient, false); + recipient = Recipient.resolved(recipient.getId()); + Log.i(TAG, "Successfully performed a UUID fetch for " + recipient.getId() + ". Registered: " + state); + } catch (IOException e) { + Log.w(TAG, "Failed to fetch a UUID for " + recipient.getId() + ". Scheduling a future fetch and building an address without one."); + ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false)); + } + } + + return new SignalServiceAddress(Optional.fromNullable(recipient.getUuid().orNull()), Optional.fromNullable(recipient.resolve().getE164().orNull())); + } +} diff --git a/src/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java b/src/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java index 32100c2d3..e25f155de 100644 --- a/src/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java +++ b/src/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.DirectoryRefreshListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -36,6 +37,7 @@ import org.whispersystems.signalservice.internal.push.LockedException; import java.io.IOException; import java.util.List; +import java.util.UUID; public final class CodeVerificationRequest { @@ -128,27 +130,34 @@ public final class CodeVerificationRequest { TextSecurePreferences.setLocalRegistrationId(context, registrationId); SessionUtil.archiveAllSessions(context); - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, credentials.getE164number(), credentials.getPassword()); + SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword()); boolean present = fcmToken != null; - accountManager.verifyAccountWithCode(code, null, registrationId, !present, pin, - unidentifiedAccessKey, universalUnidentifiedAccess); + UUID uuid = accountManager.verifyAccountWithCode(code, null, registrationId, !present, pin, unidentifiedAccessKey, universalUnidentifiedAccess); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context); List records = PreKeyUtil.generatePreKeys(context); SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(context, identityKey, true); + accountManager = AccountManagerFactory.createAuthenticated(context, uuid, credentials.getE164number(), credentials.getPassword()); accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records); if (present) { accountManager.setGcmId(Optional.fromNullable(fcmToken)); } + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + RecipientId selfId = recipientDatabase.getOrInsertFromE164(credentials.getE164number()); + + recipientDatabase.setProfileSharing(selfId, true); + recipientDatabase.markRegistered(selfId, uuid); + + TextSecurePreferences.setLocalNumber(context, credentials.getE164number()); + TextSecurePreferences.setLocalUuid(context, uuid); TextSecurePreferences.setFcmToken(context, fcmToken); TextSecurePreferences.setFcmDisabled(context, !present); TextSecurePreferences.setWebsocketRegistered(context, true); - TextSecurePreferences.setLocalNumber(context, credentials.getE164number()); DatabaseFactory.getIdentityDatabase(context) .saveIdentity(Recipient.self().getId(), @@ -161,8 +170,6 @@ public final class CodeVerificationRequest { TextSecurePreferences.setSignedPreKeyRegistered(context, true); TextSecurePreferences.setPromptedPushRegistration(context, true); TextSecurePreferences.setUnauthorizedReceived(context, false); - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.self().getId(), true); - DatabaseFactory.getRecipientDatabase(context).setRegistered(Recipient.self().getId(), RecipientDatabase.RegisteredState.REGISTERED); } public interface VerifyCallback { diff --git a/src/org/thoughtcrime/securesms/registration/service/RegistrationCodeRequest.java b/src/org/thoughtcrime/securesms/registration/service/RegistrationCodeRequest.java index e440ca916..1d9ab8bf9 100644 --- a/src/org/thoughtcrime/securesms/registration/service/RegistrationCodeRequest.java +++ b/src/org/thoughtcrime/securesms/registration/service/RegistrationCodeRequest.java @@ -50,7 +50,7 @@ public final class RegistrationCodeRequest { fcmToken = Optional.absent(); } - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, credentials.getE164number(), credentials.getPassword()); + SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword()); Optional pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, fcmToken, credentials.getE164number(), PUSH_REQUEST_TIMEOUT_MS); diff --git a/src/org/thoughtcrime/securesms/ringrtc/MessageRecipient.java b/src/org/thoughtcrime/securesms/ringrtc/MessageRecipient.java index 82901a632..e4b08fda6 100644 --- a/src/org/thoughtcrime/securesms/ringrtc/MessageRecipient.java +++ b/src/org/thoughtcrime/securesms/ringrtc/MessageRecipient.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; @@ -59,7 +60,7 @@ public final class MessageRecipient implements SignalMessageRecipient { private void sendMessage(Context context, SignalServiceCallMessage callMessage) throws UntrustedIdentityException, IOException { - messageSender.sendCallMessage(new SignalServiceAddress(recipient.requireAddress().toPhoneString()), + messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient), UnidentifiedAccessUtil.getAccessFor(context, recipient), callMessage); } diff --git a/src/org/thoughtcrime/securesms/search/SearchRepository.java b/src/org/thoughtcrime/securesms/search/SearchRepository.java index 90c759196..ee578a4e7 100644 --- a/src/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/src/org/thoughtcrime/securesms/search/SearchRepository.java @@ -13,8 +13,6 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactRepository; -import org.thoughtcrime.securesms.contacts.ContactsDatabase; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.CursorList; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SearchDatabase; diff --git a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java index c98a4c1b6..d8867e33a 100644 --- a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java +++ b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java @@ -156,7 +156,7 @@ public class IncomingMessageObserver implements ConstraintObserver.Notifier { Log.i(TAG, "Reading message..."); localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES, envelope -> { - Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource())); + Log.i(TAG, "Retrieved envelope! " + envelope.getSourceIdentifier()); try (Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) { processor.processEnvelope(envelope); } diff --git a/src/org/thoughtcrime/securesms/service/QuickResponseService.java b/src/org/thoughtcrime/securesms/service/QuickResponseService.java index d45e23f0f..32649509c 100644 --- a/src/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/src/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -7,7 +7,6 @@ import android.text.TextUtils; import android.widget.Toast; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 2ca3d37d9..4082afea3 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.service; -import android.Manifest; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; @@ -31,7 +30,6 @@ import org.signal.ringrtc.SignalMessageRecipient; import org.greenrobot.eventbus.EventBus; 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.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; @@ -40,12 +38,12 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil; 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.ringrtc.CameraState; import org.thoughtcrime.securesms.ringrtc.MessageRecipient; import org.thoughtcrime.securesms.ringrtc.CallConnectionWrapper; +import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ServiceUtil; @@ -76,7 +74,6 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.calls.BusyMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture; @@ -1019,7 +1016,7 @@ public class WebRtcCallService extends Service implements CallConnection.Observe Callable callable = new Callable() { @Override public Boolean call() throws Exception { - messageSender.sendCallMessage(new SignalServiceAddress(recipient.requireAddress().toPhoneString()), + messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(WebRtcCallService.this, recipient), UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient), callMessage); return true; diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 5c01d6800..cf562a3b4 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.sms; -import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; 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; @@ -39,7 +37,7 @@ public class IncomingTextMessage implements Parcelable { private final boolean replyPathPresent; private final String pseudoSubject; private final long sentTimestampMillis; - private final Address groupId; + private final String groupId; private final boolean push; private final int subscriptionId; private final long expiresInMillis; @@ -79,7 +77,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = unidentified; if (group.isPresent()) { - this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); + this.groupId = GroupUtil.getEncodedId(group.get().getGroupId(), false); } else { this.groupId = null; } @@ -94,7 +92,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = (in.readInt() == 1); this.pseudoSubject = in.readString(); this.sentTimestampMillis = in.readLong(); - this.groupId = in.readParcelable(IncomingTextMessage.class.getClassLoader()); + this.groupId = in.readString(); this.push = (in.readInt() == 1); this.subscriptionId = in.readInt(); this.expiresInMillis = in.readLong(); @@ -139,7 +137,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = fragments.get(0).isUnidentified(); } - protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable Address groupId) + protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable String groupId) { this.message = ""; this.sender = sender; @@ -224,7 +222,7 @@ public class IncomingTextMessage implements Parcelable { return push; } - public @Nullable Address getGroupId() { + public @Nullable String getGroupId() { return groupId; } @@ -267,7 +265,7 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(replyPathPresent ? 1 : 0); out.writeString(pseudoSubject); out.writeLong(sentTimestampMillis); - out.writeParcelable(groupId, flags); + out.writeString(groupId); out.writeInt(push ? 1 : 0); out.writeInt(subscriptionId); out.writeLong(expiresInMillis); diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 315d5469d..e55c303c5 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -26,6 +26,7 @@ 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.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; @@ -323,8 +324,7 @@ public class MessageSender { } private static boolean isGroupPushSend(Recipient recipient) { - return recipient.requireAddress().isGroup() && - !recipient.requireAddress().isMmsGroup(); + return recipient.isGroup() && !recipient.isMmsGroup(); } private static boolean isPushDestination(Context context, Recipient destination) { @@ -334,16 +334,8 @@ public class MessageSender { return false; } else { try { - SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); - Optional registeredUser = accountManager.getContact(destination.requireAddress().serialize()); - - if (!registeredUser.isPresent()) { - DatabaseFactory.getRecipientDatabase(context).setRegistered(destination.getId(), RecipientDatabase.RegisteredState.NOT_REGISTERED); - return false; - } else { - DatabaseFactory.getRecipientDatabase(context).setRegistered(destination.getId(), RecipientDatabase.RegisteredState.REGISTERED); - return true; - } + RecipientDatabase.RegisteredState state = DirectoryHelper.refreshDirectoryFor(context, destination, false); + return state == RecipientDatabase.RegisteredState.REGISTERED; } catch (IOException e1) { Log.w(TAG, e1); return false; diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java index 49c72b077..76b5a6244 100644 --- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -16,7 +16,6 @@ import android.widget.Toast; import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.WebRtcCallActivity; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; @@ -89,8 +88,8 @@ public class CommunicationActions { }.execute(); } - public static void composeSmsThroughDefaultApp(@NonNull Context context, @NonNull Address address, @Nullable String text) { - Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + address.serialize())); + public static void composeSmsThroughDefaultApp(@NonNull Context context, @NonNull Recipient recipient, @Nullable String text) { + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + recipient.requireSmsAddress())); if (text != null) { intent.putExtra("sms_body", text); } diff --git a/src/org/thoughtcrime/securesms/util/FeatureFlags.java b/src/org/thoughtcrime/securesms/util/FeatureFlags.java index 5276b3d3d..704ee96ca 100644 --- a/src/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/src/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -7,4 +7,7 @@ package org.thoughtcrime.securesms.util; public class FeatureFlags { /** Send support for view-once media. */ public static final boolean VIEW_ONCE_SENDING = false; + + /** UUID-related stuff that shouldn't be activated until the user-facing launch. */ + public static final boolean UUIDS = false; } diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java index d4d00c9d3..ff1865959 100644 --- a/src/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.util; import android.content.Context; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -15,6 +17,8 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.IOException; import java.util.Collections; @@ -51,7 +55,7 @@ public class GroupUtil { @WorkerThread public static Optional createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) { - String encodedGroupId = groupRecipient.requireAddress().toGroupString(); + String encodedGroupId = groupRecipient.requireGroupId(); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); if (!groupDatabase.isActive(encodedGroupId)) { @@ -105,8 +109,8 @@ public class GroupUtil { } else { this.members = new LinkedList<>(); - for (String member : groupContext.getMembersList()) { - this.members.add(Recipient.external(context, member)); + for (GroupContext.Member member : groupContext.getMembersList()) { + this.members.add(Recipient.externalPush(context, new SignalServiceAddress(UuidUtil.parseOrNull(member.getUuid()), member.getE164()))); } } } diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index aeba030cf..136904d85 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -9,7 +9,6 @@ import androidx.annotation.StringRes; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase; @@ -146,11 +145,11 @@ public class IdentityUtil { } } - public static void saveIdentity(Context context, String number, IdentityKey identityKey) { + public static void saveIdentity(Context context, String user, IdentityKey identityKey) { synchronized (SESSION_LOCK) { IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); SessionStore sessionStore = new TextSecureSessionStore(context); - SignalProtocolAddress address = new SignalProtocolAddress(number, 1); + SignalProtocolAddress address = new SignalProtocolAddress(user, 1); if (identityKeyStore.saveIdentity(address, identityKey)) { if (sessionStore.containsSession(address)) { @@ -166,7 +165,7 @@ public class IdentityUtil { public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) { synchronized (SESSION_LOCK) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - Recipient recipient = Recipient.external(context, verifiedMessage.getDestination()); + Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination()); Optional identityRecord = identityDatabase.getIdentity(recipient.getId()); if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) { @@ -188,7 +187,7 @@ public class IdentityUtil { (identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) || (identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED))) { - saveIdentity(context, verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()); + saveIdentity(context, verifiedMessage.getDestination().getIdentifier(), verifiedMessage.getIdentityKey()); 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 a9df4960d..4e6cbec94 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.requireAddress().serialize()); + phone.setText(p.getE164().or("")); delete.setVisibility(modifiable ? View.VISIBLE : View.GONE); delete.setOnClickListener(new View.OnClickListener() { @Override diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 9c2c69044..58157b009 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.whispersystems.libsignal.util.Medium; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.IOException; import java.security.SecureRandom; @@ -26,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.UUID; public class TextSecurePreferences { @@ -71,6 +73,7 @@ public class TextSecurePreferences { private static final String MMS_CUSTOM_USER_AGENT = "pref_custom_mms_user_agent"; private static final String THREAD_TRIM_ENABLED = "pref_trim_threads"; private static final String LOCAL_NUMBER_PREF = "pref_local_number"; + private static final String LOCAL_UUID_PREF = "pref_local_uuid"; private static final String VERIFYING_STATE_PREF = "pref_verifying"; public static final String REGISTERED_GCM_PREF = "pref_gcm_registered"; private static final String GCM_PASSWORD_PREF = "pref_gcm_password"; @@ -168,7 +171,8 @@ public class TextSecurePreferences { private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull"; private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time"; - private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate"; + private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_LEGACY = "pref_unidentified_access_certificate"; + private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate_uuid"; public static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access"; public static final String SHOW_UNIDENTIFIED_DELIVERY_INDICATORS = "pref_show_unidentifed_delivery_indicators"; private static final String UNIDENTIFIED_DELIVERY_ENABLED = "pref_unidentified_delivery_enabled"; @@ -576,10 +580,21 @@ public class TextSecurePreferences { } public static byte[] getUnidentifiedAccessCertificate(Context context) { + return parseCertificate(getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, null)); + } + + public static void setUnidentifiedAccessCertificateLegacy(Context context, byte[] value) { + setStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_LEGACY, Base64.encodeBytes(value)); + } + + public static byte[] getUnidentifiedAccessCertificateLegacy(Context context) { + return parseCertificate(getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_LEGACY, null)); + } + + private static byte[] parseCertificate(String raw) { try { - String result = getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, null); - if (result != null) { - return Base64.decode(result); + if (raw != null) { + return Base64.decode(raw); } } catch (IOException e) { Log.w(TAG, e); @@ -652,6 +667,14 @@ public class TextSecurePreferences { setStringPreference(context, LOCAL_NUMBER_PREF, localNumber); } + public static UUID getLocalUuid(Context context) { + return UuidUtil.parseOrNull(getStringPreference(context, LOCAL_UUID_PREF, null)); + } + + public static void setLocalUuid(Context context, UUID uuid) { + setStringPreference(context, LOCAL_UUID_PREF, uuid.toString()); + } + public static String getPushServerPassword(Context context) { return getStringPreference(context, GCM_PASSWORD_PREF, null); } diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 7b3419cc0..28a9ee39a 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -47,7 +47,6 @@ import com.google.i18n.phonenumbers.Phonenumber; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.components.ComposeText; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection; import org.whispersystems.libsignal.util.guava.Optional; @@ -232,13 +231,6 @@ public class Util { return totalSize; } - public static boolean isOwnNumber(Context context, Address address) { - if (address.isGroup()) return false; - if (address.isEmail()) return false; - - return TextSecurePreferences.getLocalNumber(context).equals(address.toPhoneString()); - } - public static void readFully(InputStream in, byte[] buffer) throws IOException { readFully(in, buffer, buffer.length); } diff --git a/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java b/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java index 09068dad5..e76541444 100644 --- a/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java +++ b/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java @@ -8,9 +8,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.thoughtcrime.securesms.database.Address; - -import static org.assertj.core.api.Assertions.*; +import org.whispersystems.libsignal.util.guava.Optional; import static android.provider.ContactsContract.Intents.Insert.NAME; import static android.provider.ContactsContract.Intents.Insert.EMAIL; @@ -18,7 +16,6 @@ import static android.provider.ContactsContract.Intents.Insert.PHONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @@ -27,7 +24,7 @@ public final class RecipientExporterTest { @Test public void asAddContactIntent_with_phone_number() { - Recipient recipient = givenRecipient("Alice", givenPhoneNumber("+1555123456")); + Recipient recipient = givenPhoneRecipient("Alice", "+1555123456"); Intent intent = RecipientExporter.export(recipient).asAddContactIntent(); @@ -40,7 +37,7 @@ public final class RecipientExporterTest { @Test public void asAddContactIntent_with_email() { - Recipient recipient = givenRecipient("Bob", givenEmail("bob@signal.org")); + Recipient recipient = givenEmailRecipient("Bob", "bob@signal.org"); Intent intent = RecipientExporter.export(recipient).asAddContactIntent(); @@ -51,34 +48,25 @@ public final class RecipientExporterTest { assertNull(intent.getStringExtra(PHONE)); } - @Test - public void asAddContactIntent_with_neither_email_nor_phone() { - RecipientExporter exporter = RecipientExporter.export(givenRecipient("Bob", mock(Address.class))); - - assertThatThrownBy(exporter::asAddContactIntent).isExactlyInstanceOf(RuntimeException.class) - .hasMessage("Cannot export Recipient with neither phone nor email"); - } - - private Recipient givenRecipient(String profileName, Address address) { + private Recipient givenPhoneRecipient(String profileName, String phone) { Recipient recipient = mock(Recipient.class); when(recipient.getProfileName()).thenReturn(profileName); - when(recipient.requireAddress()).thenReturn(address); + + when(recipient.requireE164()).thenReturn(phone); + when(recipient.getE164()).thenAnswer(i -> Optional.of(phone)); + when(recipient.getEmail()).thenAnswer(i -> Optional.absent()); + return recipient; } - private Address givenPhoneNumber(String phoneNumber) { - Address address = mock(Address.class); - when(address.isPhone()).thenReturn(true); - when(address.toPhoneString()).thenReturn(phoneNumber); - when(address.toEmailString()).thenThrow(new RuntimeException()); - return address; - } + private Recipient givenEmailRecipient(String profileName, String email) { + Recipient recipient = mock(Recipient.class); + when(recipient.getProfileName()).thenReturn(profileName); - private Address givenEmail(String email) { - Address address = mock(Address.class); - when(address.isEmail()).thenReturn(true); - when(address.toEmailString()).thenReturn(email); - when(address.toPhoneString()).thenThrow(new RuntimeException()); - return address; + when(recipient.requireEmail()).thenReturn(email); + when(recipient.getEmail()).thenAnswer(i -> Optional.of(email)); + when(recipient.getE164()).thenAnswer(i -> Optional.absent()); + + return recipient; } } diff --git a/witness-verifications.gradle b/witness-verifications.gradle index 0e9268aeb..be3c9ce97 100644 --- a/witness-verifications.gradle +++ b/witness-verifications.gradle @@ -354,11 +354,11 @@ dependencyVerification { ['org.signal:ringrtc-android:0.1.7.2', '7b39083b011ac754117d2b74e26ec8323278ece7656be3ba8c766cae523d378f'], - ['org.signal:signal-metadata-android:0.0.3', - '02323bc29317fa9d3b62fab0b507c94ba2e9bcc4a78d588888ffd313853757b3'], + ['org.signal:signal-metadata-android:0.0.5', + 'e79ca9231ec07b05849bc048c643fe2cec48ee781ba5aa8165847a3c90178684'], - ['org.signal:signal-metadata-java:0.0.3', - '2ce71cc4ec5dacfbaef4a265fceef61b8a09696b541994106a22a946762cbdcc'], + ['org.signal:signal-metadata-java:0.0.5', + '086841d4c494388d3102aaadc42a0af7fa8096be603213009bf2bd77920d910c'], ['org.threeten:threetenbp:1.3.6', 'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'], @@ -375,11 +375,11 @@ dependencyVerification { ['org.whispersystems:signal-protocol-java:2.7.1', '7f6df67a963acbab7716424b01b12fa7279f18a9623a2a7c8ba7b1c285830168'], - ['org.whispersystems:signal-service-android:2.13.9', - 'e0a85fa937f7ad0a446ea65405204ab69533339a78d3aa098921cf43c7997348'], + ['org.whispersystems:signal-service-android:2.14.0', + '24e7a7760f14a261d44b8d76fe6004157f7e09d7898c88ddb501ceabea47b6f6'], - ['org.whispersystems:signal-service-java:2.13.9', - '5d833e946dbbfb7b4f5dbcf26c1585376e92645aa2958503047ee7a17357897f'], + ['org.whispersystems:signal-service-java:2.14.0', + '3af7d00d1bacd5b9d73da6f287b6ce68579a8e4700ff471464a81ac93f9d4cf9'], ['pl.tajchert:waitingdots:0.1.0', '2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'],