From 4c3882689f6d2d881a035237f7cacc40b5ebc4b3 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 19 Jul 2023 17:45:11 -0400 Subject: [PATCH 01/66] Let PNP feature flag override CDS compat flag. --- .../main/java/org/thoughtcrime/securesms/util/FeatureFlags.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index ff2b0e7d51..1a394fd668 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -613,7 +613,7 @@ public static long maxAttachmentSizeBytes() { /** True if you should use CDS in compat mode (i.e. request ACI's even if you don't know the access key), otherwise false. */ public static boolean cdsCompatMode() { - return getBoolean(CDS_COMPAT_MODE, true); + return !phoneNumberPrivacy() && getBoolean(CDS_COMPAT_MODE, true); } /** True if the new conversation fragment should be used. */ From 4c428e5b5b3f3d2da539cf19010ab25148533e3e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 20 Jul 2023 10:26:11 -0400 Subject: [PATCH 02/66] Update to new CDS flag. --- .../thoughtcrime/securesms/util/FeatureFlags.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 1a394fd668..d4fbf1ed4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -106,10 +106,11 @@ public final class FeatureFlags { private static final String MAX_ATTACHMENT_RECEIVE_SIZE_BYTES = "global.attachments.maxReceiveBytes"; private static final String MAX_ATTACHMENT_SIZE_BYTES = "global.attachments.maxBytes"; private static final String SVR2_KILLSWITCH = "android.svr2.killSwitch"; - private static final String CDS_COMPAT_MODE = "global.cds.return_acis_without_uaks"; + private static final String CDS_DISABLE_COMPAT_MODE = "cds.disableCompatibilityMode"; private static final String CONVERSATION_FRAGMENT_V2 = "android.conversationFragmentV2.2"; private static final String FCM_MAY_HAVE_MESSAGES_KILL_SWITCH = "android.fcmNotificationFallbackKillSwitch"; private static final String SAFETY_NUMBER_ACI = "global.safetyNumberAci"; + /** * We will only store remote values for flags in this set. If you want a flag to be controllable * remotely, place it in here. @@ -167,7 +168,7 @@ public final class FeatureFlags { MAX_ATTACHMENT_SIZE_BYTES, AD_HOC_CALLING, SVR2_KILLSWITCH, - CDS_COMPAT_MODE, + CDS_DISABLE_COMPAT_MODE, CONVERSATION_FRAGMENT_V2, SAFETY_NUMBER_ACI, FCM_MAY_HAVE_MESSAGES_KILL_SWITCH @@ -235,7 +236,7 @@ public final class FeatureFlags { MAX_ATTACHMENT_RECEIVE_SIZE_BYTES, MAX_ATTACHMENT_SIZE_BYTES, SVR2_KILLSWITCH, - CDS_COMPAT_MODE, + CDS_DISABLE_COMPAT_MODE, CONVERSATION_FRAGMENT_V2, SAFETY_NUMBER_ACI, FCM_MAY_HAVE_MESSAGES_KILL_SWITCH @@ -613,7 +614,11 @@ public static long maxAttachmentSizeBytes() { /** True if you should use CDS in compat mode (i.e. request ACI's even if you don't know the access key), otherwise false. */ public static boolean cdsCompatMode() { - return !phoneNumberPrivacy() && getBoolean(CDS_COMPAT_MODE, true); + if (phoneNumberPrivacy()) { + return false; + } else { + return !getBoolean(CDS_DISABLE_COMPAT_MODE, false); + } } /** True if the new conversation fragment should be used. */ From 9d33690f3436a439e60421b9ad42b569a4aa8e85 Mon Sep 17 00:00:00 2001 From: Clark Chen Date: Fri, 21 Jul 2023 15:31:19 -0400 Subject: [PATCH 03/66] Show read more for super long scheduled messages. --- .../thoughtcrime/securesms/conversation/ConversationItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 12cdb80333..f6c588e3b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -1990,7 +1990,7 @@ private Spannable getLongMessageSpan(@NonNull MessageRecord messageRecord) { if (messageRecord.isMms()) { TextSlide slide = ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide(); - if (slide != null && slide.asAttachment().getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE) { + if (slide != null && (slide.asAttachment().getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE || MessageRecordUtil.isScheduled(messageRecord))) { message = getResources().getString(R.string.ConversationItem_read_more); action = () -> eventListener.onMoreTextClicked(conversationRecipient.getId(), messageRecord.getId(), messageRecord.isMms()); } else if (slide != null && slide.asAttachment().getTransferState() == AttachmentTable.TRANSFER_PROGRESS_STARTED) { From d78e73bd6f2e7d9db49dc00d1a09e8bf1203f462 Mon Sep 17 00:00:00 2001 From: Clark Date: Mon, 24 Jul 2023 13:04:10 -0400 Subject: [PATCH 04/66] Fix search showing received mention messages as note to self. --- .../securesms/components/FromTextView.java | 8 ++++++-- .../conversationlist/ConversationListItem.java | 15 ++++++++++----- .../securesms/search/SearchRepository.java | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java index f16c9a068c..2901fe7dc2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java @@ -48,9 +48,13 @@ public void setText(Recipient recipient, boolean read, @Nullable String suffix) } public void setText(Recipient recipient, @Nullable CharSequence fromString, boolean read, @Nullable String suffix) { + setText(recipient, fromString, read, suffix, true); + } + + public void setText(Recipient recipient, @Nullable CharSequence fromString, boolean read, @Nullable String suffix, boolean asThread) { SpannableStringBuilder builder = new SpannableStringBuilder(); - if (recipient.isSelf()) { + if (asThread && recipient.isSelf()) { builder.append(getContext().getString(R.string.note_to_self)); } else { builder.append(fromString); @@ -60,7 +64,7 @@ public void setText(Recipient recipient, @Nullable CharSequence fromString, bool builder.append(suffix); } - if (recipient.showVerified()) { + if (asThread && recipient.showVerified()) { Drawable official = ContextUtil.requireDrawable(getContext(), R.drawable.ic_official_20); official.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 43b05e2075..19e679922d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -325,7 +325,7 @@ public void bindMessage(@NonNull LifecycleOwner lifecycleOwner, joinMembersDisposable.dispose(); setSubjectViewText(null); - fromView.setText(recipient.get(), false); + fromView.setText(recipient.get(), recipient.get().getDisplayNameOrUsername(getContext()), false, null, false); setSubjectViewText(SearchUtil.getHighlightedSpan(locale, searchStyleFactory, messageResult.getBodySnippet(), highlightSubstring, SearchUtil.MATCH_ALL)); dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs())); archivedView.setVisibility(GONE); @@ -336,7 +336,7 @@ public void bindMessage(@NonNull LifecycleOwner lifecycleOwner, setSelectedConversations(new ConversationSet()); setBadgeFromRecipient(recipient.get()); - contactPhotoImage.setAvatar(glideRequests, recipient.get(), !batchMode); + contactPhotoImage.setAvatar(glideRequests, recipient.get(), !batchMode, true); } public void bindGroupWithMembers(@NonNull LifecycleOwner lifecycleOwner, @@ -555,12 +555,17 @@ private void onRecipientChanged(@NonNull Recipient recipient) { } if (highlightSubstring != null) { - String name = recipient.isSelf() ? getContext().getString(R.string.note_to_self) : recipient.getDisplayName(getContext()); - fromView.setText(recipient, SearchUtil.getHighlightedSpan(locale, searchStyleFactory, new SpannableString(name), highlightSubstring, SearchUtil.MATCH_ALL), true, null); + String name; + if (thread != null && recipient.isSelf()) { + name = getContext().getString(R.string.note_to_self); + } else { + name = recipient.getDisplayName(getContext()); + } + fromView.setText(recipient, SearchUtil.getHighlightedSpan(locale, searchStyleFactory, new SpannableString(name), highlightSubstring, SearchUtil.MATCH_ALL), true, null, thread != null); } else { fromView.setText(recipient, false); } - contactPhotoImage.setAvatar(glideRequests, recipient, !batchMode); + contactPhotoImage.setAvatar(glideRequests, recipient, !batchMode, thread != null); setBadgeFromRecipient(recipient); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java index e2c771b8c0..e14a1740c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java @@ -367,7 +367,7 @@ private void updateSnippetWithStyles(long id, @NonNull CharSequence body, @NonNu CharSequence updatedBody = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, body, mentions).getBody(); CharSequence updatedSnippet = makeSnippet(cleanQueries, Objects.requireNonNull(updatedBody)); - results.add(new MessageResult(record.getToRecipient(), record.getFromRecipient(), updatedBody, updatedSnippet, record.getThreadId(), record.getId(), record.getDateReceived(), true)); + results.add(new MessageResult(record.getFromRecipient(), record.getToRecipient(), updatedBody, updatedSnippet, record.getThreadId(), record.getId(), record.getDateReceived(), true)); } } } From 4adc660705b5efa404679bc313f909852bc89469 Mon Sep 17 00:00:00 2001 From: Clark Date: Tue, 25 Jul 2023 12:43:39 -0400 Subject: [PATCH 05/66] Stop content provider handler threads on release. --- .../securesms/providers/AvatarProvider.kt | 7 +++++-- .../securesms/providers/PartProvider.java | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt index b7e0e5ea7e..3b9f14280e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt @@ -15,6 +15,7 @@ import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.os.Handler +import android.os.HandlerThread import android.os.MemoryFile import android.os.ParcelFileDescriptor import android.os.ProxyFileDescriptorCallback @@ -180,7 +181,7 @@ class AvatarProvider : BaseContentProvider() { val parcelFileDescriptor = storageManager.openProxyFileDescriptor( ParcelFileDescriptor.MODE_READ_ONLY, - ProxyCallback(context!!.applicationContext, recipient), + ProxyCallback(context!!.applicationContext, recipient, handlerThread), handler ) @@ -206,7 +207,8 @@ class AvatarProvider : BaseContentProvider() { @RequiresApi(26) private class ProxyCallback( private val context: Context, - private val recipient: Recipient + private val recipient: Recipient, + private val handlerThread: HandlerThread ) : ProxyFileDescriptorCallback() { private var memoryFile: MemoryFile? = null @@ -226,6 +228,7 @@ class AvatarProvider : BaseContentProvider() { override fun onRelease() { Log.i(TAG, "${recipient.id}:onRelease") memoryFile = null + handlerThread.quitSafely() } private fun ensureResourceLoaded() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java b/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java index 2de016453e..f1d701135f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java @@ -206,7 +206,7 @@ private ParcelFileDescriptor getParcelStreamProxyForAttachment(AttachmentId atta Handler handler = new Handler(thread.getLooper()); ParcelFileDescriptor parcelFileDescriptor = storageManager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY, - new ProxyCallback(SignalDatabase.attachments(), attachmentId), + new ProxyCallback(SignalDatabase.attachments(), attachmentId, thread), handler); Log.i(TAG, attachmentId + ":createdProxy"); @@ -218,10 +218,12 @@ private static final class ProxyCallback extends ProxyFileDescriptorCallback { private AttachmentTable attachments; private AttachmentId attachmentId; + private HandlerThread handlerThread; - public ProxyCallback(@NonNull AttachmentTable attachments, @NonNull AttachmentId attachmentId) { - this.attachments = attachments; - this.attachmentId = attachmentId; + public ProxyCallback(@NonNull AttachmentTable attachments, @NonNull AttachmentId attachmentId, @NonNull HandlerThread handlerThread) { + this.attachments = attachments; + this.attachmentId = attachmentId; + this.handlerThread = handlerThread; } @Override @@ -270,6 +272,10 @@ public void onRelease() { attachments = null; attachmentId = null; + if (handlerThread != null) { + handlerThread.quitSafely(); + handlerThread = null; + } } } } From c68487c0c78c29f5a8d41d99125e28507fd4e8cd Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 26 Jul 2023 12:39:19 -0400 Subject: [PATCH 06/66] Disable ktlint rule around class naming. --- .editorconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 8ca2b5a395..e54929fbde 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,4 +5,5 @@ indent_size = 2 ij_kotlin_allow_trailing_comma_on_call_site = false ij_kotlin_allow_trailing_comma = false ktlint_code_style = intellij_idea -twitter_compose_allowed_composition_locals=LocalExtendedColors \ No newline at end of file +twitter_compose_allowed_composition_locals=LocalExtendedColors +ktlint_standard_class-naming = disabled \ No newline at end of file From 7d68a57f53eb92699a79ca9e3cefd9971f04fc43 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Thu, 27 Jul 2023 10:52:00 -0400 Subject: [PATCH 07/66] Fall back to AudioCodec if MediaRecorderWrapper fails. --- .../securesms/audio/AudioRecorder.java | 28 ++++++++++++++----- .../securesms/audio/MediaRecorderWrapper.java | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java index cf776e7df0..790176c8bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -64,11 +64,21 @@ public AudioRecorder(@NonNull Context context, @Nullable AudioRecordingHandler u } public @NonNull Single startRecording() { - Log.i(TAG, "startRecording()"); + return startRecording(Build.VERSION.SDK_INT >= 26); + } + + public @NonNull Single startRecording(final boolean useMediaCodecWrapper) { + Log.i(TAG, "startRecording(" + useMediaCodecWrapper + ")"); final SingleSubject recordingSingle = SingleSubject.create(); + startRecordingInternal(useMediaCodecWrapper, recordingSingle); + + return recordingSingle; + } + + private void startRecordingInternal(boolean useMediaRecorderWrapper, SingleSubject recordingSingle) { executor.execute(() -> { - Log.i(TAG, "Running startRecording() + " + Thread.currentThread().getId()); + Log.i(TAG, "Running startRecording(" + useMediaRecorderWrapper + ") + " + Thread.currentThread().getId()); try { if (recorder != null) { recordingSingle.onError(new IllegalStateException("We can only do one recording at a time!")); @@ -82,7 +92,7 @@ public AudioRecorder(@NonNull Context context, @Nullable AudioRecordingHandler u .withMimeType(MediaUtil.AUDIO_AAC) .createForDraftAttachmentAsync(context); - recorder = Build.VERSION.SDK_INT >= 26 ? new MediaRecorderWrapper() : new AudioCodec(); + recorder = useMediaRecorderWrapper ? new MediaRecorderWrapper() : new AudioCodec(); int focusResult = audioFocusManager.requestAudioFocus(); if (focusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { Log.w(TAG, "Could not gain audio focus. Received result code " + focusResult); @@ -90,13 +100,17 @@ public AudioRecorder(@NonNull Context context, @Nullable AudioRecordingHandler u recorder.start(fds[1]); this.recordingSubject = recordingSingle; } catch (IOException | RuntimeException e) { - recordingSingle.onError(e); - recorder = null; Log.w(TAG, e); + recordingUriFuture = null; + recorder = null; + audioFocusManager.abandonAudioFocus(); + if (useMediaRecorderWrapper) { + startRecordingInternal(false, recordingSingle); + } else { + recordingSingle.onError(e); + } } }); - - return recordingSingle; } public void stopRecording() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/MediaRecorderWrapper.java b/app/src/main/java/org/thoughtcrime/securesms/audio/MediaRecorderWrapper.java index c35f545422..d1f2cff419 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/MediaRecorderWrapper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/MediaRecorderWrapper.java @@ -41,7 +41,7 @@ public void start(ParcelFileDescriptor fileDescriptor) throws IOException { recorder.setAudioChannels(CHANNELS); recorder.prepare(); recorder.start(); - } catch (IllegalStateException e) { + } catch (RuntimeException e) { Log.w(TAG, "Unable to start recording", e); recorder.release(); recorder = null; From 8ca49c1e18ae2767753903b3121ee5291c983d2e Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 27 Jul 2023 10:28:18 -0700 Subject: [PATCH 08/66] Update to RingRTC v2.30.0 --- .../service/webrtc/GroupConnectedActionProcessor.java | 2 +- .../service/webrtc/GroupJoiningActionProcessor.java | 2 +- dependencies.gradle | 2 +- gradle/verification-metadata.xml | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java index 7ced09ad45..96820a6a18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java @@ -68,7 +68,7 @@ protected GroupConnectedActionProcessor(@NonNull MultiPeerActionProcessorFactory if (connectionState == GroupCall.ConnectionState.CONNECTED || connectionState == GroupCall.ConnectionState.CONNECTING) { if (joinState == GroupCall.JoinState.JOINED) { groupCallState = WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED; - } else if (joinState == GroupCall.JoinState.JOINING) { + } else if (joinState == GroupCall.JoinState.JOINING || joinState == GroupCall.JoinState.PENDING) { groupCallState = WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java index d10eca2437..0f61e13415 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java @@ -100,7 +100,7 @@ protected GroupJoiningActionProcessor(@NonNull MultiPeerActionProcessorFactory a .commit() .actionProcessor(actionProcessorFactory.createConnectedActionProcessor(webRtcInteractor)); - } else if (device.getJoinState() == GroupCall.JoinState.JOINING) { + } else if (device.getJoinState() == GroupCall.JoinState.JOINING || device.getJoinState() == GroupCall.JoinState.PENDING) { builder.changeCallInfoState() .groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING) .commit(); diff --git a/dependencies.gradle b/dependencies.gradle index 7c80253763..5ef1e5ec1e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -113,7 +113,7 @@ dependencyResolutionManagement { library('libsignal-client', 'org.signal', 'libsignal-client').versionRef('libsignal-client') library('libsignal-android', 'org.signal', 'libsignal-android').versionRef('libsignal-client') library('signal-aesgcmprovider', 'org.signal:aesgcmprovider:0.0.3') - library('signal-ringrtc', 'org.signal:ringrtc-android:2.29.0') + library('signal-ringrtc', 'org.signal:ringrtc-android:2.30.0') library('signal-android-database-sqlcipher', 'org.signal:sqlcipher-android:4.5.4-S2') // Third Party diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 39d7685c10..85a8748577 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -5008,12 +5008,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + From 7ff4a827559c705135472070cef769974f08539b Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 28 Jul 2023 09:47:50 -0400 Subject: [PATCH 09/66] Show popup on switching to/from speakerphone. --- .../securesms/WebRtcCallActivity.java | 16 ++++++++++++++-- .../webrtc/CallStateUpdatePopupWindow.kt | 4 +++- .../components/webrtc/WebRtcCallView.java | 7 +++---- .../components/webrtc/WebRtcCallViewModel.java | 4 ++++ app/src/main/res/drawable/symbol_speaker_24.xml | 15 +++++++++++++++ .../res/drawable/symbol_speaker_slash_24.xml | 15 +++++++++++++++ app/src/main/res/values/strings.xml | 4 ++++ 7 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/symbol_speaker_24.xml create mode 100644 app/src/main/res/drawable/symbol_speaker_slash_24.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index e61b13898e..dc1531c302 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -61,6 +61,7 @@ import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow; import org.thoughtcrime.securesms.components.webrtc.CallToastPopupWindow; import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil; +import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioDevice; import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel; @@ -833,6 +834,7 @@ public void hideSystemUI() { @Override public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) { + maybeDisplaySpeakerphonePopup(audioOutput); switch (audioOutput) { case HANDSET: handleSetAudioHandset(); @@ -853,8 +855,9 @@ public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) { @RequiresApi(31) @Override - public void onAudioOutputChanged31(@NonNull Integer audioDeviceInfo) { - ApplicationDependencies.getSignalCallManager().selectAudioDevice(new SignalAudioManager.ChosenAudioDeviceIdentifier(audioDeviceInfo)); + public void onAudioOutputChanged31(@NonNull WebRtcAudioDevice audioOutput) { + maybeDisplaySpeakerphonePopup(audioOutput.getWebRtcAudioOutput()); + ApplicationDependencies.getSignalCallManager().selectAudioDevice(new SignalAudioManager.ChosenAudioDeviceIdentifier(audioOutput.getDeviceId())); } @Override @@ -937,6 +940,15 @@ public void onNavigateUpClicked() { } } + private void maybeDisplaySpeakerphonePopup(WebRtcAudioOutput nextOutput) { + final WebRtcAudioOutput currentOutput = viewModel.getCurrentAudioOutput(); + if (currentOutput == WebRtcAudioOutput.SPEAKER && nextOutput != WebRtcAudioOutput.SPEAKER) { + callStateUpdatePopupWindow.onCallStateUpdate(CallStateUpdatePopupWindow.CallStateUpdate.SPEAKER_OFF); + } else if (currentOutput != WebRtcAudioOutput.SPEAKER && nextOutput == WebRtcAudioOutput.SPEAKER) { + callStateUpdatePopupWindow.onCallStateUpdate(CallStateUpdatePopupWindow.CallStateUpdate.SPEAKER_ON); + } + } + private class WindowLayoutInfoConsumer implements Consumer { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallStateUpdatePopupWindow.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallStateUpdatePopupWindow.kt index 79b17caea9..5af2d95172 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallStateUpdatePopupWindow.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallStateUpdatePopupWindow.kt @@ -114,6 +114,8 @@ class CallStateUpdatePopupWindow(private val parent: ViewGroup) : PopupWindow( RINGING_OFF(R.drawable.symbol_bell_slash_compact_16, R.string.CallStateUpdatePopupWindow__ringing_off), RINGING_DISABLED(null, R.string.CallStateUpdatePopupWindow__group_is_too_large), MIC_ON(R.drawable.symbol_mic_compact_16, R.string.CallStateUpdatePopupWindow__mic_on), - MIC_OFF(R.drawable.symbol_mic_slash_compact_16, R.string.CallStateUpdatePopupWindow__mic_off) + MIC_OFF(R.drawable.symbol_mic_slash_compact_16, R.string.CallStateUpdatePopupWindow__mic_off), + SPEAKER_ON(R.drawable.symbol_speaker_24, R.string.CallStateUpdatePopupWindow__speaker_on), + SPEAKER_OFF(R.drawable.symbol_speaker_slash_24, R.string.CallStateUpdatePopupWindow__speaker_off) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java index e042bbf359..5a7d6ee7a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java @@ -248,9 +248,8 @@ public void onPageSelected(int position) { runIfNonNull(controlsListener, listener -> { if (Build.VERSION.SDK_INT >= 31) { - final Integer deviceId = webRtcAudioDevice.getDeviceId(); - if (deviceId != null) { - listener.onAudioOutputChanged31(deviceId); + if (webRtcAudioDevice.getDeviceId() != null) { + listener.onAudioOutputChanged31(webRtcAudioDevice); } else { Log.e(TAG, "Attempted to change audio output to null device ID."); } @@ -1102,7 +1101,7 @@ public interface ControlsListener { void hideSystemUI(); void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput); @RequiresApi(31) - void onAudioOutputChanged31(@NonNull Integer audioOutputAddress); + void onAudioOutputChanged31(@NonNull WebRtcAudioDevice audioOutput); void onVideoChanged(boolean isVideoEnabled); void onMicChanged(boolean isMicEnabled); void onCameraDirectionChanged(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java index 756675be98..19ce09fc10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java @@ -163,6 +163,10 @@ public LiveData shouldShowSpeakerHint() { return shouldShowSpeakerHint; } + public WebRtcAudioOutput getCurrentAudioOutput() { + return getWebRtcControls().getValue().getAudioOutput(); + } + public LiveData getEphemeralState() { return ephemeralState; } diff --git a/app/src/main/res/drawable/symbol_speaker_24.xml b/app/src/main/res/drawable/symbol_speaker_24.xml new file mode 100644 index 0000000000..b2d42dda01 --- /dev/null +++ b/app/src/main/res/drawable/symbol_speaker_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/symbol_speaker_slash_24.xml b/app/src/main/res/drawable/symbol_speaker_slash_24.xml new file mode 100644 index 0000000000..c1039ceacd --- /dev/null +++ b/app/src/main/res/drawable/symbol_speaker_slash_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 549973cbcc..a304476df3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6086,6 +6086,10 @@ Mic on Mic off + + Speaker on + + Speaker off Capture Button From 82906aee58843f8cb3a1804b2c0875bd5a522153 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 28 Jul 2023 12:58:04 -0400 Subject: [PATCH 10/66] Use strongly-typed ACIs and PNIs everywhere. --- .../changenumber/ChangeNumberViewModelTest.kt | 11 +- .../database/DistributionListTablesTest.kt | 2 +- .../database/MessageTableTest_gifts.kt | 7 +- .../database/MmsTableTest_stories.kt | 7 +- .../securesms/database/RecipientTableTest.kt | 12 +- .../RecipientTableTest_getAndPossiblyMerge.kt | 164 ++++-- ...est_collapseJoinRequestEventsIfPossible.kt | 9 +- .../securesms/database/StorySendTableTest.kt | 4 +- .../database/ThreadTableTest_active.kt | 4 +- .../database/ThreadTableTest_pinned.kt | 4 +- .../database/ThreadTableTest_recents.kt | 4 +- .../messages/MessageContentProcessorTest.kt | 4 +- .../storage/ContactRecordProcessorTest.kt | 26 +- .../securesms/testing/AliceClient.kt | 2 +- .../securesms/testing/BobClient.kt | 2 +- .../securesms/testing/SignalActivityRule.kt | 2 +- .../securesms/testing/SignalDatabaseRule.kt | 4 +- .../securesms/testing/TestProtos.kt | 4 +- .../benchmark/DummyAccountManagerFactory.kt | 4 +- .../org/signal/benchmark/setup/TestUsers.kt | 2 +- .../securesms/calls/log/CallLogRow.kt | 2 +- .../changenumber/ChangeNumberLockActivity.kt | 2 +- .../changenumber/ChangeNumberRepository.kt | 5 +- .../app/changenumber/ChangeNumberViewModel.kt | 2 +- .../search/InternalSearchViewModel.kt | 3 +- .../main/UsernameLinkSettingsViewModel.kt | 2 +- .../InternalConversationSettingsFragment.kt | 30 +- .../sync/ContactDiscoveryRefreshV2.kt | 2 +- .../contacts/sync/FuzzyPhoneNumberHelper.java | 2 +- .../conversation/ConversationUpdateItem.java | 16 +- .../error/SafetyNumberChangeRepository.java | 2 +- .../storage/SignalBaseIdentityKeyStore.java | 4 +- .../storage/TextSecureSessionStore.java | 17 +- .../securesms/database/CallTable.kt | 6 +- .../securesms/database/GroupTable.kt | 27 +- .../securesms/database/IdentityTable.kt | 8 + .../securesms/database/MentionUtil.java | 2 +- .../securesms/database/MessageTable.kt | 10 +- .../securesms/database/PnpOperations.kt | 56 +- .../securesms/database/RecipientTable.kt | 508 ++++++++++-------- .../database/SenderKeySharedTable.kt | 18 +- .../securesms/database/ThreadTable.kt | 3 +- .../helpers/SignalDatabaseMigrations.kt | 7 +- .../migration/V149_LegacyMigrations.kt | 2 +- ...essageRecipientsAndEditMessageMigration.kt | 2 +- ...essageRecipientsAndEditMessageMigration.kt | 2 +- .../helpers/migration/V200_ResetPniColumn.kt | 20 + .../model/GroupCallUpdateMessageFactory.java | 4 +- .../securesms/database/model/GroupRecord.kt | 2 +- .../model/GroupsV2UpdateMessageProducer.java | 38 +- .../database/model/MessageRecord.java | 23 +- .../database/model/RecipientRecord.kt | 19 +- .../database/model/UpdateDescription.java | 24 +- .../ApplicationDependencyProvider.java | 4 +- .../securesms/groups/GroupManager.java | 6 +- .../securesms/groups/GroupManagerV2.java | 45 +- .../securesms/groups/GroupProtoUtil.java | 8 +- .../groups/GroupsV2Authorization.java | 8 +- .../securesms/groups/LiveGroup.java | 2 +- .../PendingMemberInvitesRepository.java | 2 +- .../groups/v2/GroupCandidateHelper.java | 2 +- .../securesms/groups/v2/ProfileKeySet.java | 9 +- .../v2/processing/GroupsV2StateProcessor.java | 52 +- .../jobs/AutomaticSessionResetJob.java | 2 +- .../jobs/GroupV2UpdateSelfProfileKeyJob.java | 2 +- .../jobs/MultiDeviceStorySendSyncJob.kt | 2 +- .../jobs/PushGroupSilentUpdateSendJob.java | 6 +- .../securesms/jobs/PushSendJob.java | 7 +- .../securesms/keyvalue/AccountValues.kt | 10 +- .../logsubmit/LogSectionSystemInfo.java | 2 +- .../messages/CallMessageProcessor.kt | 4 +- .../messages/DataMessageProcessor.kt | 7 +- .../messages/MessageContentProcessor.java | 10 +- .../messages/MessageContentProcessorV2.kt | 4 +- .../securesms/messages/MessageDecryptor.kt | 41 +- .../messages/SyncMessageProcessor.kt | 5 +- .../PniAccountInitializationMigrationJob.java | 2 +- .../securesms/migrations/PniMigrationJob.java | 2 +- .../migrations/UuidMigrationJob.java | 2 +- .../securesms/mms/MessageGroupContext.java | 2 +- .../securesms/push/AccountManagerFactory.java | 4 +- .../recipients/LiveRecipientCache.java | 4 +- .../securesms/recipients/Recipient.java | 54 +- .../recipients/RecipientDetails.java | 12 +- .../registration/RegistrationRepository.java | 6 +- .../service/webrtc/GroupActionProcessor.java | 3 +- .../service/webrtc/GroupCallRingCheckInfo.kt | 4 +- .../webrtc/GroupConnectedActionProcessor.java | 6 +- .../webrtc/GroupPreJoinActionProcessor.java | 3 +- .../service/webrtc/IdleActionProcessor.java | 13 +- .../IncomingGroupCallActionProcessor.java | 5 +- .../service/webrtc/SignalCallManager.java | 13 +- .../service/webrtc/WebRtcActionProcessor.java | 3 +- .../webrtc/links/SignalCallLinkManager.kt | 2 +- .../storage/ContactRecordProcessor.java | 46 +- .../securesms/storage/StorageSyncModels.java | 9 +- .../storage/StorageSyncValidations.java | 2 +- .../securesms/util/LocaleFeatureFlags.java | 6 +- .../securesms/util/UsernameUtil.java | 2 +- .../verify/VerifySafetyNumberViewModel.kt | 8 +- .../paged/SafetyNumberRepositoryTest.kt | 5 +- .../sync/FuzzyPhoneNumberHelperTest.java | 2 +- .../securesms/database/GroupTestUtil.kt | 6 +- .../database/RecipientDatabaseTestUtils.kt | 4 +- .../GroupsV2UpdateMessageProducerTest.java | 10 +- .../database/model/UpdateDescriptionTest.java | 15 +- .../groups/GroupManagerV2Test_edit.kt | 17 +- .../groups/v2/ProfileKeySetTest.java | 25 +- .../processing/GroupsV2StateProcessorTest.kt | 23 +- .../recipients/RecipientIdCacheTest.java | 39 +- .../storage/ContactRecordProcessorTest.kt | 142 ++--- .../storage/StorageSyncHelperTest.java | 17 +- .../databaseprotos/DecryptedGroupHelper.kt | 21 +- dependencies.gradle | 2 +- gradle/verification-metadata.xml | 24 + .../api/SignalServiceAccountManager.java | 10 +- .../api/SignalServiceMessageReceiver.java | 4 +- .../api/SignalServiceMessageSender.java | 4 +- .../api/crypto/SignalServiceCipher.java | 8 +- .../api/groupsv2/GroupsV2Api.java | 3 +- .../api/groupsv2/GroupsV2Operations.java | 3 +- .../api/messages/SignalServiceContent.java | 45 +- .../api/messages/SignalServiceEnvelope.java | 15 +- .../SignalServicePniSignatureMessage.java | 2 +- .../signalservice/api/push/ACI.java | 47 -- .../signalservice/api/push/PNI.java | 38 -- .../signalservice/api/push/ServiceId.java | 120 ----- .../signalservice/api/push/ServiceId.kt | 181 +++++++ .../signalservice/api/push/ServiceIds.java | 5 +- .../api/services/CdsiV2Service.java | 6 +- .../api/services/ProfileService.java | 4 +- .../api/storage/SignalAccountRecord.java | 6 +- .../api/storage/SignalContactRecord.java | 36 +- .../SignalStoryDistributionListRecord.java | 10 +- .../api/util/CredentialsProvider.java | 4 +- .../internal/push/IdentityCheckResponse.java | 2 +- .../internal/push/PushServiceSocket.java | 2 +- .../push/SendGroupMessageResponse.java | 1 + .../signalservice/internal/util/JsonUtil.java | 2 +- .../util/StaticCredentialsProvider.java | 4 +- .../main/proto/InternalSerialization.proto | 4 +- .../src/main/proto/StorageService.proto | 22 +- .../DeviceContactsInputStreamTest.java | 5 +- .../api/storage/SignalContactRecordTest.java | 15 +- ...lServiceAddressProtobufSerializerTest.java | 5 +- .../java/org/signal/util/SignalClient.kt | 22 +- 146 files changed, 1405 insertions(+), 1183 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V200_ResetPniColumn.kt delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.java create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt index ed95f6c680..4757e4f830 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.testing.success import org.thoughtcrime.securesms.testing.timeout import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.internal.push.MismatchedDevices import org.whispersystems.signalservice.internal.push.PreKeyState import java.util.UUID @@ -73,7 +74,7 @@ class ChangeNumberViewModelTest { fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() { // GIVEN val aci = Recipient.self().requireServiceId() - val newPni = ServiceId.from(UUID.randomUUID()) + val newPni = PNI.from(UUID.randomUUID()) lateinit var changeNumberRequest: ChangePhoneNumberRequest lateinit var setPreKeysRequest: PreKeyState @@ -180,7 +181,7 @@ class ChangeNumberViewModelTest { val aci = Recipient.self().requireServiceId() val oldPni = Recipient.self().requirePni() val oldE164 = Recipient.self().requireE164() - val newPni = ServiceId.from(UUID.randomUUID()) + val newPni = PNI.from(UUID.randomUUID()) lateinit var changeNumberRequest: ChangePhoneNumberRequest lateinit var setPreKeysRequest: PreKeyState @@ -225,7 +226,7 @@ class ChangeNumberViewModelTest { fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() { // GIVEN val aci = Recipient.self().requireServiceId() - val newPni = ServiceId.from(UUID.randomUUID()) + val newPni = PNI.from(UUID.randomUUID()) lateinit var changeNumberRequest: ChangePhoneNumberRequest lateinit var setPreKeysRequest: PreKeyState @@ -269,7 +270,7 @@ class ChangeNumberViewModelTest { fun testChangeNumber_givenMismatchedDevicesOnFirstCall() { // GIVEN val aci = Recipient.self().requireServiceId() - val newPni = ServiceId.from(UUID.randomUUID()) + val newPni = PNI.from(UUID.randomUUID()) lateinit var changeNumberRequest: ChangePhoneNumberRequest lateinit var setPreKeysRequest: PreKeyState @@ -313,7 +314,7 @@ class ChangeNumberViewModelTest { fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() { // GIVEN val aci = Recipient.self().requireServiceId() - val newPni = ServiceId.from(UUID.randomUUID()) + val newPni = PNI.from(UUID.randomUUID()) lateinit var changeNumberRequest: ChangePhoneNumberRequest lateinit var setPreKeysRequest: PreKeyState diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt index dde6f7a7f9..df616f9c70 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt @@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListId import org.thoughtcrime.securesms.database.model.DistributionListRecord import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.UUID class DistributionListTablesTest { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt index cb60fa48fd..12a7cede4f 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt @@ -10,9 +10,8 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.UUID @Suppress("ClassName") @@ -34,7 +33,7 @@ class MessageTableTest_gifts { SignalStore.account().setAci(localAci) SignalStore.account().setPni(localPni) - recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) } + recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) } } @Test diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt index 7f626bdedf..4c863af0e2 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt @@ -16,9 +16,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mms.IncomingMediaMessage import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.UUID import java.util.concurrent.TimeUnit @@ -45,7 +44,7 @@ class MmsTableTest_stories { SignalStore.account().setPni(localPni) myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY)) - recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) } + recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) } releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient()) SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt index e9559c9941..e17b679af9 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt @@ -13,8 +13,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.FeatureFlagsAccessor -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.UUID @RunWith(AndroidJUnit4::class) @@ -173,10 +173,10 @@ class RecipientTableTest { SignalDatabase.recipients.markUnregistered(mainId) - val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get() val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() - val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get() assertEquals(mainId, byAci) assertEquals(byE164, byPni) @@ -192,10 +192,10 @@ class RecipientTableTest { SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!) - val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get() val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() - val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get() assertEquals(mainId, byAci) assertEquals(byE164, byPni) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt index afc28b1e7f..5bf7284e00 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt @@ -7,6 +7,7 @@ import org.hamcrest.MatcherAssert import org.hamcrest.Matchers import org.junit.Assert import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -14,6 +15,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.signal.core.util.SqlUtil +import org.signal.core.util.exists import org.signal.core.util.requireLong import org.signal.core.util.requireNonNullString import org.signal.core.util.select @@ -36,14 +38,13 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage import org.thoughtcrime.securesms.sms.IncomingTextMessage import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.FeatureFlagsAccessor -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.Optional import java.util.UUID @@ -59,6 +60,21 @@ class RecipientTableTest_getAndPossiblyMerge { FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true) } + @Test + fun single() { + test("merge, e164 + pni reassigned, aci abandoned") { + given(E164_A, PNI_A, ACI_A) + given(E164_B, PNI_B, ACI_B) + + process(E164_A, PNI_A, ACI_B) + + expect(null, null, ACI_A) + expect(E164_A, PNI_A, ACI_B) + + expectChangeNumberEvent() + } + } + @Test fun allNonMergeTests() { test("e164-only insert") { @@ -69,7 +85,7 @@ class RecipientTableTest_getAndPossiblyMerge { assertEquals(RecipientTable.RegisteredState.UNKNOWN, record.registered) } - test("pni-only insert") { + test("pni-only insert", exception = IllegalArgumentException::class.java) { val id = process(null, PNI_A, null) expect(null, PNI_A, null) @@ -84,6 +100,21 @@ class RecipientTableTest_getAndPossiblyMerge { val record = SignalDatabase.recipients.getRecord(id) assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered) } + + test("e164+pni insert") { + process(E164_A, PNI_A, null) + expect(E164_A, PNI_A, null) + } + + test("e164+aci insert") { + process(E164_A, null, ACI_A) + expect(E164_A, null, ACI_A) + } + + test("e164+pni+aci insert") { + process(E164_A, PNI_A, ACI_A) + expect(E164_A, PNI_A, ACI_A) + } } @Test @@ -167,6 +198,12 @@ class RecipientTableTest_getAndPossiblyMerge { expectSessionSwitchoverEvent(E164_A) } + test("e164 and pni matches, all provided, new aci, existing pni session, pni-verified") { + given(E164_A, PNI_A, null, pniSession = true) + process(E164_A, PNI_A, ACI_A, pniVerified = true) + expect(E164_A, PNI_A, ACI_A) + } + test("e164 and aci matches, all provided, new pni") { given(E164_A, null, ACI_A) process(E164_A, PNI_A, ACI_A) @@ -309,6 +346,26 @@ class RecipientTableTest_getAndPossiblyMerge { expectChangeNumberEvent() } + test("steal, pni is changed") { + given(E164_A, PNI_B, ACI_A) + given(E164_B, PNI_A, null) + + process(E164_A, PNI_A, null) + + expect(E164_A, PNI_A, ACI_A) + expect(E164_B, null, null) + } + + test("steal, pni is changed, aci left behind") { + given(E164_B, PNI_A, ACI_A) + given(E164_A, PNI_B, null) + + process(E164_A, PNI_A, null) + + expect(E164_B, null, ACI_A) + expect(E164_A, PNI_A, null) + } + test("steal, e164+pni & e164+pni, no aci provided, no pni session") { given(E164_A, PNI_B, null) given(E164_B, PNI_A, null) @@ -502,7 +559,7 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent(E164_A) } - test("merge, e164+pni & aci, pni session, thread merge shadows") { + test("merge, e164+pni & aci, pni session, thread merge shadows SSE") { given(E164_A, PNI_A, null, pniSession = true) given(null, null, ACI_A) @@ -600,6 +657,18 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent(E164_A) } + test("merge, e164 + pni reassigned, aci abandoned") { + given(E164_A, PNI_A, ACI_A) + given(E164_B, PNI_B, ACI_B) + + process(E164_A, PNI_A, ACI_B) + + expect(null, null, ACI_A) + expect(E164_A, PNI_A, ACI_B) + + expectChangeNumberEvent() + } + test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") { given(E164_SELF, null, ACI_SELF) given(null, null, ACI_A) @@ -768,9 +837,15 @@ class RecipientTableTest_getAndPossiblyMerge { } private fun identityKey(value: Byte): IdentityKey { + val byteArray = ByteArray(32) + byteArray[0] = value + return identityKey(byteArray) + } + + private fun identityKey(value: ByteArray): IdentityKey { val bytes = ByteArray(33) bytes[0] = 0x05 - bytes[1] = value + value.copyInto(bytes, 1) return IdentityKey(bytes) } @@ -873,8 +948,8 @@ class RecipientTableTest_getAndPossiblyMerge { generatedIds += id if (createThread) { // Create a thread and throw a dummy message in it so it doesn't get automatically deleted - SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id)) - SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, (Math.random() * Long.MAX_VALUE).toLong(), 0, 0, "", Optional.empty(), 0, false, ""), "")) + val result = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = id, time = (Math.random() * 10000000).toLong(), body = "1")) + SignalDatabase.threads.markAsActiveEarly(result.get().threadId) } if (pniSession) { @@ -885,11 +960,34 @@ class RecipientTableTest_getAndPossiblyMerge { SignalDatabase.sessions.store(pni, SignalProtocolAddress(pni.toString(), 1), SessionRecord()) } + if (aci != null) { + SignalDatabase.identities.saveIdentity( + addressName = aci.toString(), + recipientId = id, + identityKey = identityKey(Util.getSecretBytes(32)), + verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT, + firstUse = true, + timestamp = 0, + nonBlockingApproval = false + ) + } + if (pni != null) { + SignalDatabase.identities.saveIdentity( + addressName = pni.toString(), + recipientId = id, + identityKey = identityKey(Util.getSecretBytes(32)), + verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT, + firstUse = true, + timestamp = 0, + nonBlockingApproval = false + ) + } + return id } fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false, pniVerified: Boolean = false): RecipientId { - outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf) + outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf) generatedIds += outputRecipientId return outputRecipientId } @@ -903,15 +1001,15 @@ class RecipientTableTest_getAndPossiblyMerge { val expected = RecipientTuple( e164 = e164, pni = pni, - serviceId = aci ?: pni + aci = aci ) val actual = RecipientTuple( e164 = recipient.e164.orElse(null), pni = recipient.pni.orElse(null), - serviceId = recipient.serviceId.orElse(null) + aci = recipient.aci.orElse(null) ) - assertEquals(expected, actual) + assertEquals("Recipient $id did not match expected result!", expected, actual) } fun expectDeleted() { @@ -919,21 +1017,21 @@ class RecipientTableTest_getAndPossiblyMerge { } fun expectDeleted(id: RecipientId) { - SignalDatabase.rawDatabase - .select("1") - .from(RecipientTable.TABLE_NAME) + val found = SignalDatabase.rawDatabase + .exists(RecipientTable.TABLE_NAME) .where("${RecipientTable.ID} = ?", id) .run() - .use { !it.moveToFirst() } + + assertFalse("Expected $id to be deleted, but it's still present!", found) } fun expectChangeNumberEvent() { - assertEquals(1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId)) + assertEquals("Missing change number event!", 1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId)) changeNumberExpected = true } fun expectNoChangeNumberEvent() { - assertEquals(0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId)) + assertEquals("Unexpected change number event!", 0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId)) changeNumberExpected = false } @@ -943,42 +1041,39 @@ class RecipientTableTest_getAndPossiblyMerge { fun expectSessionSwitchoverEvent(recipientId: RecipientId, e164: String) { val event: SessionSwitchoverEvent? = getLatestSessionSwitchoverEvent(recipientId) - assertNotNull(event) + assertNotNull("Missing session switchover event! Expected one with e164 = $e164", event) assertEquals(e164, event!!.e164) sessionSwitchoverExpected = true } fun expectNoSessionSwitchoverEvent() { - assertNull(getLatestSessionSwitchoverEvent(outputRecipientId)) + assertNull("Unexpected session switchover event!", getLatestSessionSwitchoverEvent(outputRecipientId)) } fun expectThreadMergeEvent(previousE164: String) { val event: ThreadMergeEvent? = getLatestThreadMergeEvent(outputRecipientId) - assertNotNull(event) - assertEquals(previousE164, event!!.previousE164) + assertNotNull("Missing thread merge event! Expected one with e164 = $previousE164", event) + assertEquals("E164 on thread merge event doesn't match!", previousE164, event!!.previousE164) threadMergeExpected = true } fun expectNoThreadMergeEvent() { - assertNull(getLatestThreadMergeEvent(outputRecipientId)) + assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId)) } private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId { - val serviceIdString: String? = (aci ?: pni)?.toString() - val pniString: String? = pni?.toString() - val id: Long = SignalDatabase.rawDatabase.insert( RecipientTable.TABLE_NAME, null, contentValuesOf( RecipientTable.PHONE to e164, - RecipientTable.SERVICE_ID to serviceIdString, - RecipientTable.PNI_COLUMN to pniString, + RecipientTable.ACI_COLUMN to aci?.toString(), + RecipientTable.PNI_COLUMN to pni?.toString(), RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id ) ) - assertTrue("Failed to insert! E164: $e164, ServiceId: $serviceIdString, PNI: $pniString", id > 0) + assertTrue("Failed to insert! E164: $e164, ACI: $aci, PNI: $pni", id > 0) return RecipientId.from(id) } @@ -987,14 +1082,14 @@ class RecipientTableTest_getAndPossiblyMerge { data class RecipientTuple( val e164: String?, val pni: PNI?, - val serviceId: ServiceId? + val aci: ACI? ) { /** * The intent here is to give nice diffs with the name of the constants rather than the values. */ override fun toString(): String { - return "(${e164.e164String()}, ${pni.pniString()}, ${serviceId.serviceIdString()})" + return "(${e164.e164String()}, ${pni.pniString()}, ${aci.aciString()})" } private fun String?.e164String(): String { @@ -1018,12 +1113,9 @@ class RecipientTableTest_getAndPossiblyMerge { } ?: "null" } - private fun ServiceId?.serviceIdString(): String { + private fun ACI?.aciString(): String { return this?.let { when (it) { - PNI_A -> "PNI_A" - PNI_B -> "PNI_B" - PNI_SELF -> "PNI_SELF" ACI_A -> "ACI_A" ACI_B -> "ACI_B" ACI_SELF -> "ACI_SELF" diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt index a67316513c..d1e1080799 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt @@ -21,9 +21,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage import org.thoughtcrime.securesms.sms.IncomingTextMessage -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.Optional import java.util.UUID @@ -283,8 +282,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible { } companion object { - private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e")) - private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed")) + private val aliceServiceId: ACI = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e")) + private val bobServiceId: ACI = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed")) private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) private val groupId = GroupId.v2(masterKey) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt index e8ee5b2ec8..5064e8d653 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.testing.SignalActivityRule import org.whispersystems.signalservice.api.push.DistributionId -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.UUID @RunWith(AndroidJUnit4::class) @@ -465,7 +465,7 @@ class StorySendTableTest { private fun makeRecipients(count: Int): List { return (1..count).map { - SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) + SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) } } } diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_active.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_active.kt index 55e8251ea2..0385e39175 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_active.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_active.kt @@ -14,7 +14,7 @@ import org.junit.Test import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testing.SignalDatabaseRule -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.UUID @Suppress("ClassName") @@ -28,7 +28,7 @@ class ThreadTableTest_active { @Before fun setUp() { - recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))) + recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))) } @Test diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_pinned.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_pinned.kt index 8a5165baac..5efcac8834 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_pinned.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_pinned.kt @@ -9,7 +9,7 @@ import org.signal.core.util.CursorUtil import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testing.SignalDatabaseRule -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.UUID @Suppress("ClassName") @@ -23,7 +23,7 @@ class ThreadTableTest_pinned { @Before fun setUp() { - recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))) + recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))) } @Test diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_recents.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_recents.kt index 5e0058f984..d112370fd4 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_recents.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/ThreadTableTest_recents.kt @@ -10,7 +10,7 @@ import org.signal.core.util.CursorUtil import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.testing.SignalDatabaseRule -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.UUID @Suppress("ClassName") @@ -25,7 +25,7 @@ class ThreadTableTest_recents { @Before fun setUp() { - recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))) + recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))) } @Test diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt index 2e65da6bca..4a28ecc122 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt @@ -46,9 +46,9 @@ abstract class MessageContentProcessorTest { ): SignalServiceContentProto { return TestProtos.build { serviceContent( - localAddress = address(uuid = harness.self.requireServiceId().uuid()).build(), + localAddress = address(uuid = harness.self.requireServiceId().rawUuid).build(), metadata = metadata( - address = address(uuid = messageSender.requireServiceId().uuid()).build() + address = address(uuid = messageSender.requireServiceId().rawUuid).build() ).build() ).apply { content = content().apply { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt index e5919a777c..9e81f991d9 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt @@ -14,8 +14,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.FeatureFlagsAccessor -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.storage.SignalContactRecord import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.internal.storage.protos.ContactRecord @@ -39,14 +39,14 @@ class ContactRecordProcessorTest { setStorageId(originalId, STORAGE_ID_A) val remote1 = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) + setAci(ACI_A.toString()) setUnregisteredAtTimestamp(100) } val remote2 = buildRecord(STORAGE_ID_C) { - setServiceId(PNI_A.toString()) - setServicePni(PNI_A.toString()) - setServiceE164(E164_A) + setAci(PNI_A.toString()) + setPni(PNI_A.toString()) + setE164(E164_A) } // WHEN @@ -54,10 +54,10 @@ class ContactRecordProcessorTest { subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR) // THEN - val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get() val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() - val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get() assertEquals(originalId, byAci) assertEquals(byE164, byPni) @@ -71,14 +71,14 @@ class ContactRecordProcessorTest { setStorageId(originalId, STORAGE_ID_A) val remote1 = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) + setAci(ACI_A.toString()) setUnregisteredAtTimestamp(0) } val remote2 = buildRecord(STORAGE_ID_C) { - setServiceId(PNI_A.toString()) - setServicePni(PNI_A.toString()) - setServiceE164(E164_A) + setAci(PNI_A.toString()) + setPni(PNI_A.toString()) + setE164(E164_A) } // WHEN @@ -86,7 +86,7 @@ class ContactRecordProcessorTest { subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR) // THEN - val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get() val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt index 615430b425..0977d99211 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt @@ -27,7 +27,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK private val aliceSenderCertificate = FakeClientHelpers.createCertificateFor( trustRoot = trustRoot, - uuid = serviceId.uuid(), + uuid = serviceId.rawUuid, e164 = e164, deviceId = 1, identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey, diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt index e53cd147d2..954a248302 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt @@ -50,7 +50,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair: private val serviceAddress = SignalServiceAddress(serviceId, e164) private val registrationId = KeyHelper.generateRegistrationId(false) private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair) - private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.uuid(), e164, 1, identityKeyPair.publicKey.publicKey, 31337) + private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.rawUuid, e164, 1, identityKeyPair.publicKey.publicKey, 31337) private val sessionLock = object : SignalSessionLock { private val lock = ReentrantLock() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt index 1f8b31c8a8..89a97893d9 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt @@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.registration.VerifyResponse import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.api.profiles.SignalServiceProfile -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.ServiceResponseProcessor diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt index 343d5f864e..4adebe0b15 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt @@ -4,8 +4,8 @@ import org.junit.rules.TestWatcher import org.junit.runner.Description import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.UUID /** diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt index 3c9c13f308..706e589eea 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.testing import com.google.protobuf.ByteString import org.signal.libsignal.zkgroup.groups.GroupMasterKey -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2 @@ -17,7 +17,7 @@ class TestProtos private constructor() { uuid: UUID = UUID.randomUUID() ): AddressProto.Builder { return AddressProto.newBuilder() - .setUuid(ServiceId.from(uuid).toByteString()) + .setUuid(ACI.from(uuid).toByteString()) } fun metadata( diff --git a/app/src/benchmark/java/org/signal/benchmark/DummyAccountManagerFactory.kt b/app/src/benchmark/java/org/signal/benchmark/DummyAccountManagerFactory.kt index bebf6bbfe9..27f51b9f03 100644 --- a/app/src/benchmark/java/org/signal/benchmark/DummyAccountManagerFactory.kt +++ b/app/src/benchmark/java/org/signal/benchmark/DummyAccountManagerFactory.kt @@ -7,8 +7,8 @@ import org.thoughtcrime.securesms.push.AccountManagerFactory import org.thoughtcrime.securesms.util.FeatureFlags import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.account.PreKeyUpload -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration import java.io.IOException import java.util.Optional diff --git a/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt b/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt index 0bb7c6bf4e..167e4eb862 100644 --- a/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt +++ b/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt @@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil import org.thoughtcrime.securesms.registration.VerifyResponse import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.api.profiles.SignalServiceProfile -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.ServiceResponseProcessor diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt index baa2f14019..c3c46fefb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt @@ -93,7 +93,7 @@ sealed class CallLogRow { return FULL } - if (groupCallUpdateDetails.inCallUuidsList.contains(Recipient.self().requireServiceId().uuid().toString())) { + if (groupCallUpdateDetails.inCallUuidsList.contains(Recipient.self().requireAci().rawUuid.toString())) { return LOCAL_USER_JOINED } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt index 41e4df1dff..e736c76c07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt @@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme import org.thoughtcrime.securesms.util.DynamicTheme -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.Objects private val TAG: String = Log.tag(ChangeNumberLockActivity::class.java) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index 2926ccfb09..4699c3cc28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -34,8 +34,7 @@ import org.whispersystems.signalservice.api.SvrNoDataException import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest import org.whispersystems.signalservice.api.account.PreKeyUpload import org.whispersystems.signalservice.api.kbs.MasterKey -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceIdType import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignedPreKeyEntity @@ -243,7 +242,7 @@ class ChangeNumberRepository( throw AssertionError("No change number metadata") } - val originalPni = ServiceId.fromByteString(metadata.previousPni) + val originalPni = PNI.parseOrThrow(metadata.previousPni) if (originalPni == pni) { Log.i(TAG, "No change has occurred, PNI is unchanged: $pni") diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt index cc717b4452..ba752d8279 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewMod import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet import org.thoughtcrime.securesms.util.DefaultValueLiveData -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException import org.whispersystems.signalservice.internal.ServiceResponse import java.util.Objects diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/search/InternalSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/search/InternalSearchViewModel.kt index 87c15d5c28..7e91b4435b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/search/InternalSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/search/InternalSearchViewModel.kt @@ -8,7 +8,6 @@ package org.thoughtcrime.securesms.components.settings.app.internal.search import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -46,7 +45,7 @@ class InternalSearchViewModel : ViewModel() { InternalSearchResult( id = record.id, name = record.displayName(), - aci = record.serviceId?.toString(), + aci = record.aci?.toString(), pni = record.pni.toString(), groupId = record.groupId ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt index b66ff20163..dea0f1c751 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsViewModel.kt @@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.UsernameUtil -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import java.io.IOException diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index bc0ef7c561..f608b6f175 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -165,8 +165,11 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( .setTitle("Are you sure?") .setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() } .setPositiveButton(android.R.string.ok) { _, _ -> - if (recipient.hasServiceId()) { - SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requireServiceId().toString()) + if (recipient.hasAci()) { + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requireAci().toString()) + } + if (recipient.hasPni()) { + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requirePni().toString()) } } .show() @@ -182,14 +185,25 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( .setTitle("Are you sure?") .setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() } .setPositiveButton(android.R.string.ok) { _, _ -> + SignalDatabase.threads.deleteConversation(SignalDatabase.threads.getThreadIdIfExistsFor(recipient.id)) + if (recipient.hasServiceId()) { SignalDatabase.recipients.debugClearServiceIds(recipient.id) SignalDatabase.recipients.debugClearProfileData(recipient.id) - SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requireServiceId().toString()) - ApplicationDependencies.getProtocolStore().aci().identities().delete(recipient.requireServiceId().toString()) - ApplicationDependencies.getProtocolStore().pni().identities().delete(recipient.requireServiceId().toString()) - SignalDatabase.threads.deleteConversation(SignalDatabase.threads.getThreadIdIfExistsFor(recipient.id)) } + + if (recipient.hasAci()) { + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requireAci().toString()) + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requirePni(), addressName = recipient.requireAci().toString()) + ApplicationDependencies.getProtocolStore().aci().identities().delete(recipient.requireAci().toString()) + } + + if (recipient.hasPni()) { + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requireAci(), addressName = recipient.requirePni().toString()) + SignalDatabase.sessions.deleteAllFor(serviceId = SignalStore.account().requirePni(), addressName = recipient.requirePni().toString()) + ApplicationDependencies.getProtocolStore().aci().identities().delete(recipient.requirePni().toString()) + } + startActivity(MainActivity.clearTop(requireContext())) } .show() @@ -237,7 +251,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( SignalDatabase.recipients.debugClearE164AndPni(recipient.id) val splitRecipientId: RecipientId = if (FeatureFlags.phoneNumberPrivacy()) { - SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.pni.orElse(null), recipient.pni.orElse(null), recipient.requireE164()) + SignalDatabase.recipients.getAndPossiblyMergePnpVerified(null, recipient.pni.orElse(null), recipient.requireE164()) } else { SignalDatabase.recipients.getAndPossiblyMerge(recipient.pni.orElse(null), recipient.requireE164()) } @@ -281,7 +295,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( SignalDatabase.recipients.debugRemoveAci(recipient.id) - val aciRecipientId: RecipientId = SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.requireServiceId(), null, null) + val aciRecipientId: RecipientId = SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.requireAci(), null, null) recipient.profileKey?.let { profileKey -> SignalDatabase.recipients.setProfileKey(aciRecipientId, ProfileKey(profileKey)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt index fa2b6ad982..3a764a58e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.FeatureFlags -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException import org.whispersystems.signalservice.api.services.CdsiV2Service diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelper.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelper.java index 80e7c9beb0..3ed8a5708e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelper.java @@ -4,7 +4,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Arrays; import java.util.Collection; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index 17e6205fb4..9e6ba2ed6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -50,7 +50,7 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Collection; import java.util.Locale; @@ -315,7 +315,7 @@ public void observe(@NonNull LifecycleOwner lifecycleOwner, @Nullable Recipient liveBannedMembers = liveGroup.getBannedMembers(); liveFullMembers = Transformations.map(liveGroup.getFullMembers(), members -> members.stream() - .map(m -> m.getMember().requireServiceId().uuid()) + .map(m -> m.getMember().requireAci().getRawUuid()) .collect(Collectors.toSet())); liveIsSelfAdmin.observe(lifecycleOwner, updater); @@ -343,7 +343,7 @@ public boolean isBanned(Recipient recipient) { Set bannedMembers = liveBannedMembers.getValue(); if (bannedMembers != null) { - return recipient.getServiceId().isPresent() && bannedMembers.contains(recipient.requireServiceId().uuid()); + return recipient.getServiceId().isPresent() && bannedMembers.contains(recipient.requireServiceId().getRawUuid()); } return false; } @@ -355,7 +355,7 @@ public boolean isFullMember(Recipient recipient) { Set members = liveFullMembers.getValue(); if (members != null) { - return recipient.getServiceId().isPresent() && members.contains(recipient.requireServiceId().uuid()); + return recipient.hasAci() && members.contains(recipient.requireAci().getRawUuid()); } return false; } @@ -446,12 +446,12 @@ private void present(@NonNull ConversationMessage conversationMessage, } }); } else if (conversationMessage.getMessageRecord().isGroupCall()) { - UpdateDescription updateDescription = MessageRecord.getGroupCallUpdateDescription(getContext(), conversationMessage.getMessageRecord().getBody(), true); - Collection sids = updateDescription.getMentioned(); + UpdateDescription updateDescription = MessageRecord.getGroupCallUpdateDescription(getContext(), conversationMessage.getMessageRecord().getBody(), true); + Collection acis = updateDescription.getMentioned(); int text = 0; - if (Util.hasItems(sids)) { - if (sids.contains(SignalStore.account().requireAci())) { + if (Util.hasItems(acis)) { + if (acis.contains(SignalStore.account().requireAci())) { text = R.string.ConversationUpdateItem_return_to_call; } else if (GroupCallUpdateDetailsUtil.parse(conversationMessage.getMessageRecord().getBody()).getIsCallFull()) { text = R.string.ConversationUpdateItem_call_is_full; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java index 069b65358f..31589b2ef6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java @@ -176,7 +176,7 @@ private TrustAndVerifyResult trustOrVerifyChangedRecipientsAndResendInternal(@No Log.d(TAG, "Saving identity result: " + result); if (result == SignalIdentityKeyStore.SaveResult.NO_CHANGE) { Log.i(TAG, "Archiving sessions explicitly as they appear to be out of sync."); - ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID); + ApplicationDependencies.getProtocolStore().aci().sessions().archiveSessions(changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID); ApplicationDependencies.getProtocolStore().aci().sessions().archiveSiblingSessions(mismatchAddress); SignalDatabase.senderKeyShared().deleteAllFor(changedRecipient.getRecipient().getId()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java index 5cf4c7b570..c725ae28af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java @@ -69,8 +69,8 @@ public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityK public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) { try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - IdentityStoreRecord identityRecord = cache.get(address.getName()); - RecipientId recipientId = RecipientId.fromSidOrE164(address.getName()); + IdentityStoreRecord identityRecord = cache.get(address.getName()); + RecipientId recipientId = RecipientId.fromSidOrE164(address.getName()); if (identityRecord == null) { Log.i(TAG, "Saving new identity for " + address); diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index 4a8a914bbb..da84874432 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -6,7 +6,6 @@ import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.signal.libsignal.protocol.message.CiphertextMessage; import org.signal.libsignal.protocol.state.SessionRecord; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; import org.thoughtcrime.securesms.database.SessionTable; @@ -126,13 +125,23 @@ public void archiveSession(SignalProtocolAddress address) { } } } + + public void archiveSession(@NonNull ServiceId serviceId, int deviceId) { + try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { + archiveSession(new SignalProtocolAddress(serviceId.toString(), deviceId)); + } + } - public void archiveSession(@NonNull RecipientId recipientId, int deviceId) { + public void archiveSessions(@NonNull RecipientId recipientId, int deviceId) { try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { Recipient recipient = Recipient.resolved(recipientId); - if (recipient.hasServiceId()) { - archiveSession(new SignalProtocolAddress(recipient.requireServiceId().toString(), deviceId)); + if (recipient.hasAci()) { + archiveSession(new SignalProtocolAddress(recipient.requireAci().toString(), deviceId)); + } + + if (recipient.hasPni()) { + archiveSession(new SignalProtocolAddress(recipient.requirePni().toString(), deviceId)); } if (recipient.hasE164()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt index 0e8481bc90..fc51ab6d75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt @@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.jobs.CallSyncEventJob import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent import java.util.UUID import java.util.concurrent.TimeUnit @@ -605,11 +605,11 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl fun insertOrUpdateGroupCallFromRingState( ringId: Long, groupRecipientId: RecipientId, - ringerUUID: UUID, + ringerAci: ACI, dateReceived: Long, ringState: RingUpdate ) { - val ringerRecipient = Recipient.externalPush(ServiceId.from(ringerUUID)) + val ringerRecipient = Recipient.externalPush(ringerAci) handleGroupRingState(ringId, groupRecipientId, ringerRecipient.id, dateReceived, ringState) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt index c846c02808..eb229c6dab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt @@ -55,6 +55,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.util.UuidUtil import java.io.Closeable import java.security.SecureRandom @@ -1215,7 +1216,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT val serviceId = recipient.serviceId return if (serviceId.isPresent) { - DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, serviceId.get().uuid()) + DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, serviceId.get().rawUuid) .map { it.role == Member.Role.ADMINISTRATOR } .orElse(false) } else { @@ -1232,7 +1233,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT return MemberLevel.NOT_A_MEMBER } - var memberLevel: Optional = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, serviceId.get().uuid()) + var memberLevel: Optional = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, serviceId.get().rawUuid) .map { member -> if (member.role == Member.Role.ADMINISTRATOR) { MemberLevel.ADMINISTRATOR @@ -1242,12 +1243,12 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } if (memberLevel.isAbsent()) { - memberLevel = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, serviceId.get().uuid()) + memberLevel = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, serviceId.get().rawUuid) .map { MemberLevel.PENDING_MEMBER } } if (memberLevel.isAbsent()) { - memberLevel = DecryptedGroupUtil.findRequestingByUuid(decryptedGroup.requestingMembersList, serviceId.get().uuid()) + memberLevel = DecryptedGroupUtil.findRequestingByUuid(decryptedGroup.requestingMembersList, serviceId.get().rawUuid) .map { _ -> MemberLevel.REQUESTING_MEMBER } } @@ -1264,7 +1265,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT fun getMemberRecipientIds(memberSet: MemberSet): List { val includeSelf = memberSet.includeSelf - val selfUuid = SignalStore.account().requireAci().uuid() + val selfAciUuid = SignalStore.account().requireAci().rawUuid val recipients: MutableList = ArrayList(decryptedGroup.membersCount + decryptedGroup.pendingMembersCount) var unknownMembers = 0 @@ -1273,8 +1274,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT for (uuid in DecryptedGroupUtil.toUuidList(decryptedGroup.membersList)) { if (UuidUtil.UNKNOWN_UUID == uuid) { unknownMembers++ - } else if (includeSelf || selfUuid != uuid) { - recipients += RecipientId.from(ServiceId.from(uuid)) + } else if (includeSelf || selfAciUuid != uuid) { + recipients += RecipientId.from(ACI.from(uuid)) } } @@ -1282,8 +1283,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT for (uuid in DecryptedGroupUtil.pendingToUuidList(decryptedGroup.pendingMembersList)) { if (UuidUtil.UNKNOWN_UUID == uuid) { unknownPending++ - } else if (includeSelf || selfUuid != uuid) { - recipients += RecipientId.from(ServiceId.from(uuid)) + } else if (includeSelf || selfAciUuid != uuid) { + recipients += RecipientId.from(ACI.from(uuid)) } } } @@ -1301,7 +1302,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .asSequence() .map { UuidUtil.fromByteStringOrNull(it.uuid) } .filterNotNull() - .map { ServiceId.from(it) } + .map { ACI.from(it) } .sortedBy { it.toString() } .toList() } @@ -1376,8 +1377,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT private fun gv2GroupActive(decryptedGroup: DecryptedGroup): Boolean { val aci = SignalStore.account().requireAci() - return DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, aci.uuid()).isPresent || - DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, aci.uuid()).isPresent + return DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, aci.rawUuid).isPresent || + DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, aci.rawUuid).isPresent } private fun List.toRecipientIds(): MutableList { @@ -1405,7 +1406,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT Log.w(TAG, "Saw an unknown UUID when mapping to RecipientIds!") null } else { - val id = RecipientId.from(ServiceId.from(uuid)) + val id = RecipientId.from(ACI.from(uuid)) val remapped = RemappedRecords.getInstance().getRecipient(id) if (remapped.isPresent) { Log.w(TAG, "Saw that $id remapped to $remapped. Using the mapping.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt index 7b865f1e7c..55178915a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt @@ -70,6 +70,14 @@ class IdentityTable internal constructor(context: Context?, databaseHelper: Sign """ } + fun getIdentityStoreRecord(serviceId: ServiceId?): IdentityStoreRecord? { + return if (serviceId != null) { + getIdentityStoreRecord(serviceId.toString()) + } else { + null + } + } + fun getIdentityStoreRecord(addressName: String): IdentityStoreRecord? { readableDatabase .select() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MentionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/database/MentionUtil.java index cc267f7205..002275d85b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MentionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MentionUtil.java @@ -96,7 +96,7 @@ private MentionUtil() { } BodyRangeList.Builder builder = BodyRangeList.newBuilder(); for (Mention mention : mentions) { - String uuid = Recipient.resolved(mention.getRecipientId()).requireServiceId().toString(); + String uuid = Recipient.resolved(mention.getRecipientId()).requireAci().toString(); builder.addRanges(BodyRangeList.BodyRange.newBuilder() .setMentionUuid(uuid) .setStart(mention.getStart()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index f555cabf93..1bd13d5684 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -142,6 +142,7 @@ import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.isStory import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage import java.io.Closeable import java.io.IOException @@ -845,7 +846,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat val threadId = threads.getOrCreateThreadIdFor(recipient) val messageId: MessageId = writableDatabase.withinTransaction { db -> val self = Recipient.self() - val markRead = joinedUuids.contains(self.requireServiceId().uuid()) || self.id == sender + val markRead = joinedUuids.contains(self.requireServiceId().rawUuid) || self.id == sender val updateDetails: ByteArray = GroupCallUpdateDetails.newBuilder() .setEraId(eraId) .setStartedCallUuid(Recipient.resolved(sender).requireServiceId().toString()) @@ -919,7 +920,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } val updateDetail = GroupCallUpdateDetailsUtil.parse(message.body) - val containsSelf = joinedUuids.contains(SignalStore.account().requireAci().uuid()) + val containsSelf = joinedUuids.contains(SignalStore.account().requireAci().rawUuid) val sameEraId = updateDetail.eraId == eraId && !Util.isEmpty(eraId) val inCallUuids = if (sameEraId) joinedUuids.map { it.toString() } else emptyList() val contentValues = contentValuesOf( @@ -954,7 +955,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat MmsReader(cursor).use { reader -> val record = reader.getNext() ?: return@withinTransaction false val groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(record.body) - val containsSelf = peekJoinedUuids.contains(SignalStore.account().requireAci().uuid()) + val containsSelf = peekJoinedUuids.contains(SignalStore.account().requireAci().rawUuid) val sameEraId = groupCallUpdateDetails.eraId == peekGroupCallEraId && !Util.isEmpty(peekGroupCallEraId) val inCallUuids = if (sameEraId) { @@ -3088,9 +3089,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat val members: MutableSet = mutableSetOf() if (message.isGroupUpdate && message.isV2Group) { + // TODO [greyson][ServiceId] pending members could be ACI's or PNI's members += message.requireGroupV2Properties().allActivePendingAndRemovedMembers .distinct() - .map { uuid -> RecipientId.from(ServiceId.from(uuid)) } + .map { uuid -> RecipientId.from(ACI.from(uuid)) } .toList() members -= Recipient.self().id diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt index 04d65abfaf..59bcfb0ec8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt @@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.database import app.cash.exhaustive.Exhaustive import org.thoughtcrime.securesms.database.model.RecipientRecord import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI /** * Encapsulates data around processing a tuple of user data into a user entry in [RecipientTable]. @@ -15,18 +16,20 @@ data class PnpDataSet( val pni: PNI?, val aci: ACI?, val byE164: RecipientId?, - val byPniSid: RecipientId?, - val byPniOnly: RecipientId?, - val byAciSid: RecipientId?, + val byPni: RecipientId?, + val byAci: RecipientId?, val e164Record: RecipientRecord? = null, - val pniSidRecord: RecipientRecord? = null, - val aciSidRecord: RecipientRecord? = null + val pniRecord: RecipientRecord? = null, + val aciRecord: RecipientRecord? = null ) { /** * @return The common id if all non-null ids are equal, or null if all are null or at least one non-null pair doesn't match. */ - val commonId: RecipientId? = findCommonId(listOf(byE164, byPniSid, byPniOnly, byAciSid)) + val commonId: RecipientId? = findCommonId(listOf(byE164, byPni, byAci)) + + /** The ID that would be used to contact this user. */ + val serviceId: ServiceId? = aci ?: pni fun MutableSet.replace(recipientId: RecipientId, update: (RecipientRecord) -> RecipientRecord) { val toUpdate = this.first { it.id == recipientId } @@ -43,7 +46,7 @@ data class PnpDataSet( return this } - val records: MutableSet = listOfNotNull(e164Record, pniSidRecord, aciSidRecord).toMutableSet() + val records: MutableSet = listOfNotNull(e164Record, pniRecord, aciRecord).toMutableSet() for (operation in operations) { @Exhaustive @@ -55,16 +58,12 @@ data class PnpDataSet( records.replace(operation.recipientId) { record -> record.copy( pni = null, - serviceId = if (record.sidIsPni()) { - null - } else { - record.serviceId - } + aci = record.aci ) } } is PnpOperation.SetAci -> { - records.replace(operation.recipientId) { it.copy(serviceId = operation.aci) } + records.replace(operation.recipientId) { it.copy(aci = operation.aci) } } is PnpOperation.SetE164 -> { records.replace(operation.recipientId) { it.copy(e164 = operation.e164) } @@ -72,12 +71,7 @@ data class PnpDataSet( is PnpOperation.SetPni -> { records.replace(operation.recipientId) { record -> record.copy( - pni = operation.pni, - serviceId = if (record.sidIsPni()) { - operation.pni - } else { - record.serviceId ?: operation.pni - } + pni = operation.pni ) } } @@ -89,7 +83,7 @@ data class PnpDataSet( primary.copy( e164 = primary.e164 ?: secondary.e164, pni = primary.pni ?: secondary.pni, - serviceId = primary.serviceId ?: secondary.serviceId + aci = primary.aci ?: secondary.aci ) } @@ -101,20 +95,16 @@ data class PnpDataSet( } val newE164Record = if (e164 != null) records.firstOrNull { it.e164 == e164 } else null - val newPniSidRecord = if (pni != null) records.firstOrNull { it.serviceId == pni } else null - val newAciSidRecord = if (aci != null) records.firstOrNull { it.serviceId == aci } else null + val newPniRecord = if (pni != null) records.firstOrNull { it.pni == pni } else null + val newAciRecord = if (aci != null) records.firstOrNull { it.aci == aci } else null - return PnpDataSet( - e164 = e164, - pni = pni, - aci = aci, + return this.copy( byE164 = newE164Record?.id, - byPniSid = newPniSidRecord?.id, - byPniOnly = byPniOnly, - byAciSid = newAciSidRecord?.id, + byPni = newPniRecord?.id, + byAci = newAciRecord?.id, e164Record = newE164Record, - pniSidRecord = newPniSidRecord, - aciSidRecord = newAciSidRecord + pniRecord = newPniRecord, + aciRecord = newAciRecord ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index e8382cb85f..813a9267f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -104,9 +104,9 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaper import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory import org.thoughtcrime.securesms.wallpaper.WallpaperStorage import org.whispersystems.signalservice.api.profiles.SignalServiceProfile -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.storage.SignalAccountRecord import org.whispersystems.signalservice.api.storage.SignalContactRecord @@ -134,7 +134,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da const val TABLE_NAME = "recipient" const val ID = "_id" - const val SERVICE_ID = "uuid" + const val ACI_COLUMN = "aci" const val PNI_COLUMN = "pni" const val USERNAME = "username" const val PHONE = "phone" @@ -202,7 +202,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da """ CREATE TABLE $TABLE_NAME ( $ID INTEGER PRIMARY KEY AUTOINCREMENT, - $SERVICE_ID TEXT UNIQUE DEFAULT NULL, + $ACI_COLUMN TEXT UNIQUE DEFAULT NULL, $USERNAME TEXT UNIQUE DEFAULT NULL, $PHONE TEXT UNIQUE DEFAULT NULL, $EMAIL TEXT UNIQUE DEFAULT NULL, @@ -266,12 +266,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val CREATE_INDEXS = arrayOf( "CREATE INDEX IF NOT EXISTS recipient_group_type_index ON $TABLE_NAME ($GROUP_TYPE);", "CREATE UNIQUE INDEX IF NOT EXISTS recipient_pni_index ON $TABLE_NAME ($PNI_COLUMN)", - "CREATE INDEX IF NOT EXISTS recipient_service_id_profile_key ON $TABLE_NAME ($SERVICE_ID, $PROFILE_KEY) WHERE $SERVICE_ID NOT NULL AND $PROFILE_KEY NOT NULL" + "CREATE INDEX IF NOT EXISTS recipient_service_id_profile_key ON $TABLE_NAME ($ACI_COLUMN, $PROFILE_KEY) WHERE $ACI_COLUMN NOT NULL AND $PROFILE_KEY NOT NULL" ) private val RECIPIENT_PROJECTION: Array = arrayOf( ID, - SERVICE_ID, + ACI_COLUMN, PNI_COLUMN, USERNAME, PHONE, @@ -424,17 +424,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } fun getByServiceId(serviceId: ServiceId): Optional { - return getByColumn(SERVICE_ID, serviceId.toString()) + return when (serviceId) { + is ACI -> getByAci(serviceId) + is PNI -> getByPni(serviceId) + } } - fun getByCallLinkRoomId(callLinkRoomId: CallLinkRoomId): Optional { - return getByColumn(CALL_LINK_ROOM_ID, callLinkRoomId.serialize()) + fun getByAci(aci: ACI): Optional { + return getByColumn(ACI_COLUMN, aci.toString()) } - /** - * Will return a recipient matching the PNI, but only in the explicit [PNI_COLUMN]. This should only be checked in conjunction with [getByServiceId] as a way - * to avoid creating a recipient we already merged. - */ fun getByPni(pni: PNI): Optional { return getByColumn(PNI_COLUMN, pni.toString()) } @@ -443,35 +442,39 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return getByColumn(USERNAME, username) } + fun getByCallLinkRoomId(callLinkRoomId: CallLinkRoomId): Optional { + return getByColumn(CALL_LINK_ROOM_ID, callLinkRoomId.serialize()) + } + fun isAssociated(serviceId: ServiceId, pni: PNI): Boolean { - return readableDatabase.exists(TABLE_NAME).where("$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString()).run() + return readableDatabase.exists(TABLE_NAME).where("$ACI_COLUMN = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString()).run() } @JvmOverloads fun getAndPossiblyMerge(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId { - require(!(serviceId == null && e164 == null)) { "Must provide an ACI or E164!" } - return getAndPossiblyMerge(serviceId = serviceId, pni = null, e164 = e164, pniVerified = false, changeSelf = changeSelf) + require(serviceId != null || e164 != null) { "Must provide an ACI or E164!" } + return when (serviceId) { + is ACI -> getAndPossiblyMerge(aci = serviceId, pni = null, e164 = e164, pniVerified = false, changeSelf = changeSelf) + is PNI -> getAndPossiblyMerge(aci = null, pni = serviceId, e164 = e164, pniVerified = false, changeSelf = changeSelf) + else -> getAndPossiblyMerge(aci = null, pni = null, e164 = e164, pniVerified = false, changeSelf = changeSelf) + } } /** * Gets and merges a (serviceId, pni, e164) tuple, doing merges/updates as needed, and giving you back the final RecipientId. * It is assumed that the tuple is verified. Do not give this method an untrusted association. */ - fun getAndPossiblyMergePnpVerified(serviceId: ServiceId?, pni: PNI?, e164: String?): RecipientId { + fun getAndPossiblyMergePnpVerified(aci: ACI?, pni: PNI?, e164: String?): RecipientId { if (!FeatureFlags.phoneNumberPrivacy()) { throw AssertionError() } - return getAndPossiblyMerge(serviceId = serviceId, pni = pni, e164 = e164, pniVerified = true, changeSelf = false) + return getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, pniVerified = true, changeSelf = false) } @VisibleForTesting - fun getAndPossiblyMerge(serviceId: ServiceId?, pni: PNI?, e164: String?, pniVerified: Boolean = false, changeSelf: Boolean = false): RecipientId { - require(!(serviceId == null && e164 == null)) { "Must provide an ACI or E164!" } - - if ((serviceId is PNI) && pni != null && serviceId != pni) { - throw AssertionError("Provided two non-matching PNIs! serviceId: $serviceId, pni: $pni") - } + fun getAndPossiblyMerge(aci: ACI?, pni: PNI?, e164: String?, pniVerified: Boolean = false, changeSelf: Boolean = false): RecipientId { + require(aci != null || e164 != null) { "Must provide an ACI or E164!" } val db = writableDatabase var transactionSuccessful = false @@ -479,18 +482,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da db.beginTransaction() try { - result = when { - serviceId is ACI -> processPnpTuple(e164 = e164, pni = pni, aci = serviceId, pniVerified = pniVerified, changeSelf = changeSelf) - serviceId is PNI -> processPnpTuple(e164 = e164, pni = serviceId, aci = null, pniVerified = pniVerified, changeSelf = changeSelf) - serviceId == null -> processPnpTuple(e164 = e164, pni = pni, aci = null, pniVerified = pniVerified, changeSelf = changeSelf) - serviceId == pni -> processPnpTuple(e164 = e164, pni = pni, aci = null, pniVerified = pniVerified, changeSelf = changeSelf) - pni != null -> processPnpTuple(e164 = e164, pni = pni, aci = ACI.from(serviceId.uuid()), pniVerified = pniVerified, changeSelf = changeSelf) - getByPni(PNI.from(serviceId.uuid())).isPresent -> processPnpTuple(e164 = e164, pni = PNI.from(serviceId.uuid()), aci = null, pniVerified = pniVerified, changeSelf = changeSelf) - else -> processPnpTuple(e164 = e164, pni = pni, aci = ACI.fromNullable(serviceId), pniVerified = pniVerified, changeSelf = changeSelf) - } + result = processPnpTuple(e164 = e164, pni = pni, aci = aci, pniVerified = pniVerified, changeSelf = changeSelf) if (result.operations.isNotEmpty() || result.requiredInsert) { - Log.i(TAG, "[getAndPossiblyMerge] ($serviceId, $pni, $e164) BreadCrumbs: ${result.breadCrumbs}, Operations: ${result.operations}, RequiredInsert: ${result.requiredInsert}, FinalId: ${result.finalId}") + Log.i(TAG, "[getAndPossiblyMerge] ($aci, $pni, $e164) BreadCrumbs: ${result.breadCrumbs}, Operations: ${result.operations}, RequiredInsert: ${result.requiredInsert}, FinalId: ${result.finalId}") } db.setTransactionSuccessful() @@ -525,17 +520,17 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val serviceIdToProfileKey: MutableMap = mutableMapOf() readableDatabase - .select(SERVICE_ID, PROFILE_KEY) + .select(ACI_COLUMN, PROFILE_KEY) .from(TABLE_NAME) - .where("$SERVICE_ID NOT NULL AND $PROFILE_KEY NOT NULL") + .where("$ACI_COLUMN NOT NULL AND $PROFILE_KEY NOT NULL") .run() .use { cursor -> while (cursor.moveToNext()) { - val serviceId: ServiceId? = ServiceId.parseOrNull(cursor.requireString(SERVICE_ID)) + val aci: ACI? = ACI.parseOrNull(cursor.requireString(ACI_COLUMN)) val profileKey: ProfileKey? = ProfileKeyUtil.profileKeyOrNull(cursor.requireString(PROFILE_KEY)) - if (serviceId != null && profileKey != null) { - serviceIdToProfileKey[serviceId] = profileKey + if (aci != null && profileKey != null) { + serviceIdToProfileKey[aci] = profileKey } } } @@ -827,19 +822,19 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da if (id < 0) { Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.") if (FeatureFlags.phoneNumberPrivacy()) { - recipientId = getAndPossiblyMergePnpVerified(if (insert.serviceId.isValid) insert.serviceId else null, insert.pni.orElse(null), insert.number.orElse(null)) + recipientId = getAndPossiblyMergePnpVerified(if (insert.aci.isValid) insert.aci else null, insert.pni.orElse(null), insert.number.orElse(null)) } else { - recipientId = getAndPossiblyMerge(if (insert.serviceId.isValid) insert.serviceId else null, insert.number.orElse(null)) + recipientId = getAndPossiblyMerge(if (insert.aci.isValid) insert.aci else null, insert.number.orElse(null)) } db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId)) } else { recipientId = RecipientId.from(id) } - if (insert.identityKey.isPresent && insert.serviceId.isValid) { + if (insert.identityKey.isPresent && insert.aci.isValid) { try { val identityKey = IdentityKey(insert.identityKey.get(), 0) - identities.updateIdentityAfterSync(insert.serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState)) + identities.updateIdentityAfterSync(insert.aci.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState)) } catch (e: InvalidKeyException) { Log.w(TAG, "Failed to process identity key during insert! Skipping.", e) } @@ -868,9 +863,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.") if (FeatureFlags.phoneNumberPrivacy()) { - recipientId = getAndPossiblyMergePnpVerified(if (update.new.serviceId.isValid) update.new.serviceId else null, update.new.pni.orElse(null), update.new.number.orElse(null)) + recipientId = getAndPossiblyMergePnpVerified(if (update.new.aci.isValid) update.new.aci else null, update.new.pni.orElse(null), update.new.number.orElse(null)) } else { - recipientId = getAndPossiblyMerge(if (update.new.serviceId.isValid) update.new.serviceId else null, update.new.number.orElse(null)) + recipientId = getAndPossiblyMerge(if (update.new.aci.isValid) update.new.aci else null, update.new.number.orElse(null)) } Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId") @@ -887,9 +882,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da try { val oldIdentityRecord = identityStore.getIdentityRecord(recipientId) - if (update.new.identityKey.isPresent && update.new.serviceId.isValid) { + if (update.new.identityKey.isPresent && update.new.aci.isValid) { val identityKey = IdentityKey(update.new.identityKey.get(), 0) - identities.updateIdentityAfterSync(update.new.serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState)) + identities.updateIdentityAfterSync(update.new.aci.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState)) } val newIdentityRecord = identityStore.getIdentityRecord(recipientId) @@ -1108,7 +1103,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da private fun getRecordForSync(query: String?, args: Array?): List { val table = """ - $TABLE_NAME LEFT OUTER JOIN ${IdentityTable.TABLE_NAME} ON $TABLE_NAME.$SERVICE_ID = ${IdentityTable.TABLE_NAME}.${IdentityTable.ADDRESS} + $TABLE_NAME LEFT OUTER JOIN ${IdentityTable.TABLE_NAME} ON $TABLE_NAME.$ACI_COLUMN = ${IdentityTable.TABLE_NAME}.${IdentityTable.ADDRESS} LEFT OUTER JOIN ${GroupTable.TABLE_NAME} ON $TABLE_NAME.$GROUP_ID = ${GroupTable.TABLE_NAME}.${GroupTable.GROUP_ID} LEFT OUTER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} """ @@ -1152,7 +1147,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da .where( """ $STORAGE_SERVICE_ID NOT NULL AND ( - ($GROUP_TYPE = ? AND $SERVICE_ID NOT NULL AND $ID != ?) + ($GROUP_TYPE = ? AND $ACI_COLUMN NOT NULL AND $ID != ?) OR $GROUP_TYPE = ? OR @@ -2004,7 +1999,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da Log.w(TAG, "[setPhoneNumber] Hit a conflict when trying to update $id. Possibly merging.") val existing: RecipientRecord = getRecord(id) - val newId = getAndPossiblyMerge(existing.serviceId, e164) + val newId = getAndPossiblyMerge(existing.aci, e164) Log.w(TAG, "[setPhoneNumber] Resulting id: $newId") db.setTransactionSuccessful() @@ -2054,7 +2049,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * Associates the provided IDs together. The assumption here is that all of the IDs correspond to the local user and have been verified. */ fun linkIdsForSelf(aci: ACI, pni: PNI, e164: String) { - val id: RecipientId = getAndPossiblyMerge(serviceId = aci, pni = pni, e164 = e164, changeSelf = true, pniVerified = true) + val id: RecipientId = getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, changeSelf = true, pniVerified = true) updatePendingSelfData(id) } @@ -2067,7 +2062,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da db.beginTransaction() try { val id = Recipient.self().id - val newId = getAndPossiblyMerge(serviceId = SignalStore.account().requireAci(), pni = pni, e164 = e164, pniVerified = true, changeSelf = true) + val newId = getAndPossiblyMerge(aci = SignalStore.account().requireAci(), pni = pni, e164 = e164, pniVerified = true, changeSelf = true) if (id == newId) { Log.i(TAG, "[updateSelfPhone] Phone updated for self") @@ -2170,8 +2165,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da fun setPni(id: RecipientId, pni: PNI) { writableDatabase .update(TABLE_NAME) - .values(SERVICE_ID to pni.toString()) - .where("$ID = ? AND ($SERVICE_ID IS NULL OR $SERVICE_ID = $PNI_COLUMN)", id) + .values(ACI_COLUMN to pni.toString()) + .where("$ID = ? AND ($ACI_COLUMN IS NULL OR $ACI_COLUMN = $PNI_COLUMN)", id) .run() writableDatabase @@ -2212,7 +2207,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da fun markRegisteredOrThrow(id: RecipientId, serviceId: ServiceId) { val contentValues = contentValuesOf( REGISTERED to RegisteredState.REGISTERED.id, - SERVICE_ID to serviceId.toString().lowercase(), + ACI_COLUMN to serviceId.toString().lowercase(), UNREGISTERED_TIMESTAMP to 0 ) if (update(id, contentValues)) { @@ -2226,7 +2221,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da if (FeatureFlags.phoneNumberPrivacy()) { val record = getRecord(id) - if (record.pni != null && record.serviceId != record.pni) { + if (record.aci != null && record.pni != null) { markUnregisteredAndSplit(id, record) } else { markUnregisteredWithoutSplit(id) @@ -2241,7 +2236,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * This is to allow a new user to register the number with a new ACI. */ private fun markUnregisteredAndSplit(id: RecipientId, record: RecipientRecord) { - check(record.pni != null && record.pni != record.serviceId) + check(record.aci != null && record.pni != null) val contentValues = contentValuesOf( REGISTERED to RegisteredState.NOT_REGISTERED.id, @@ -2256,7 +2251,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) } - val splitId = getAndPossiblyMerge(record.pni, record.pni, record.e164) + val splitId = getAndPossiblyMerge(null, record.pni, record.e164) Log.i(TAG, "Split off new recipient as $splitId (ACI-only recipient is $id)") } @@ -2284,7 +2279,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da check(FeatureFlags.phoneNumberPrivacy()) val record = getByStorageId(storageId)!! - check(record.serviceId != null && record.pni != null && record.serviceId != record.pni) + check(record.aci != null && record.pni != null) writableDatabase .update(TABLE_NAME) @@ -2295,7 +2290,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da .where("$ID = ?", record.id) .run() - getAndPossiblyMerge(record.pni, record.pni, record.e164) + getAndPossiblyMerge(null, record.pni, record.e164) } fun bulkUpdatedRegisteredStatus(registered: Map, unregistered: Collection) { @@ -2308,7 +2303,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da put(REGISTERED, RegisteredState.REGISTERED.id) put(UNREGISTERED_TIMESTAMP, 0) if (serviceId != null) { - put(SERVICE_ID, serviceId.toString().lowercase()) + put(ACI_COLUMN, serviceId.toString().lowercase()) } } @@ -2344,12 +2339,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da db.beginTransaction() try { for ((e164, aci) in mapping) { - var aciEntry = if (aci != null) getByServiceId(aci) else Optional.empty() + var aciEntry = if (aci != null) getByAci(aci) else Optional.empty() if (aciEntry.isPresent) { val idChanged = setPhoneNumber(aciEntry.get(), e164) if (idChanged) { - aciEntry = getByServiceId(aci!!) + aciEntry = getByAci(aci!!) } } @@ -2380,7 +2375,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da db.beginTransaction() try { for ((e164, result) in mapping) { - ids += getAndPossiblyMerge(serviceId = result.aci, pni = result.pni, e164 = e164, pniVerified = false, changeSelf = false) + ids += getAndPossiblyMerge(aci = result.aci, pni = result.pni, e164 = e164, pniVerified = false, changeSelf = false) } db.setTransactionSuccessful() @@ -2492,12 +2487,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da .run() } is PnpOperation.RemovePni -> { - writableDatabase - .update(TABLE_NAME) - .values(SERVICE_ID to null) - .where("$ID = ? AND $SERVICE_ID NOT NULL AND $SERVICE_ID = $PNI_COLUMN", operation.recipientId) - .run() - writableDatabase .update(TABLE_NAME) .values(PNI_COLUMN to null) @@ -2508,7 +2497,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da writableDatabase .update(TABLE_NAME) .values( - SERVICE_ID to operation.aci.toString(), + ACI_COLUMN to operation.aci.toString(), REGISTERED to RegisteredState.REGISTERED.id, UNREGISTERED_TIMESTAMP to 0 ) @@ -2525,8 +2514,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da is PnpOperation.SetPni -> { writableDatabase .update(TABLE_NAME) - .values(SERVICE_ID to operation.pni.toString()) - .where("$ID = ? AND ($SERVICE_ID IS NULL OR $SERVICE_ID = $PNI_COLUMN)", operation.recipientId) + .values(ACI_COLUMN to operation.pni.toString()) + .where("$ID = ? AND ($ACI_COLUMN IS NULL OR $ACI_COLUMN = $PNI_COLUMN)", operation.recipientId) .run() writableDatabase @@ -2596,9 +2585,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da pni = pni, aci = aci, byE164 = e164?.let { getByE164(it).orElse(null) }, - byPniSid = pni?.let { getByServiceId(it).orElse(null) }, - byPniOnly = pni?.let { getByPni(it).orElse(null) }, - byAciSid = aci?.let { getByServiceId(it).orElse(null) } + byPni = pni?.let { getByPni(it).orElse(null) }, + byAci = aci?.let { getByAci(it).orElse(null) } ) val allRequiredDbFields: MutableList = mutableListOf() @@ -2606,13 +2594,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da allRequiredDbFields += partialData.byE164 } if (aci != null) { - allRequiredDbFields += partialData.byAciSid + allRequiredDbFields += partialData.byAci } if (pni != null) { - allRequiredDbFields += partialData.byPniOnly - } - if (pni != null && aci == null) { - allRequiredDbFields += partialData.byPniSid + allRequiredDbFields += partialData.byPni } val allRequiredDbFieldPopulated: Boolean = allRequiredDbFields.all { it != null } @@ -2630,7 +2615,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } // Nothing matches - if (partialData.byE164 == null && partialData.byPniSid == null && partialData.byAciSid == null) { + if (partialData.byE164 == null && partialData.byPni == null && partialData.byAci == null) { breadCrumbs += "NothingMatches" return PnpChangeSet( id = PnpIdResolver.PnpInsert( @@ -2642,8 +2627,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - // TODO pni only record? - // At this point, we know that records have been found for at least two of the fields, // and that there are at least two unique IDs among the records. // @@ -2653,40 +2636,40 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da breadCrumbs += "NeedsMerge" - val fullData = partialData.copy( + val preMergeData = partialData.copy( e164Record = partialData.byE164?.let { getRecord(it) }, - pniSidRecord = partialData.byPniSid?.let { getRecord(it) }, - aciSidRecord = partialData.byAciSid?.let { getRecord(it) } + pniRecord = partialData.byPni?.let { getRecord(it) }, + aciRecord = partialData.byAci?.let { getRecord(it) } ) - check(fullData.commonId == null) - check(listOfNotNull(fullData.byE164, fullData.byPniSid, fullData.byPniOnly, fullData.byAciSid).size >= 2) + check(preMergeData.commonId == null) + check(listOfNotNull(preMergeData.byE164, preMergeData.byPni, preMergeData.byAci).size >= 2) val operations: LinkedHashSet = linkedSetOf() - operations += processPossibleE164PniSidMerge(pni, pniVerified, fullData, breadCrumbs) - operations += processPossiblePniSidAciSidMerge(e164, pni, aci, fullData.perform(operations), changeSelf, breadCrumbs) - operations += processPossibleE164AciSidMerge(e164, pni, aci, fullData.perform(operations), changeSelf, breadCrumbs) + operations += processPossibleE164PniMerge(preMergeData, pniVerified, changeSelf, breadCrumbs) + operations += processPossiblePniAciMerge(preMergeData.perform(operations), pniVerified, changeSelf, breadCrumbs) + operations += processPossibleE164AciMerge(preMergeData.perform(operations), pniVerified, changeSelf, breadCrumbs) - val finalData: PnpDataSet = fullData.perform(operations) - val primaryId: RecipientId = listOfNotNull(finalData.byAciSid, finalData.byE164, finalData.byPniSid).first() + val postMergeData: PnpDataSet = preMergeData.perform(operations) + val primaryId: RecipientId = listOfNotNull(postMergeData.byAci, postMergeData.byE164, postMergeData.byPni).first() - if (finalData.byAciSid == null && aci != null) { + if (postMergeData.byAci == null && aci != null) { breadCrumbs += "FinalUpdateAci" operations += PnpOperation.SetAci( recipientId = primaryId, aci = aci ) - if (!pniVerified && finalData.pni != null && sessions.hasAnySessionFor(finalData.pni.toString())) { + if (!pniVerified && postMergeData.pni != null && sessions.hasAnySessionFor(postMergeData.pni.toString())) { operations += PnpOperation.SessionSwitchoverInsert( recipientId = primaryId, - e164 = finalData.e164 + e164 = postMergeData.e164 ) } } - if (finalData.byE164 == null && e164 != null && (changeSelf || notSelf(e164, pni, aci))) { + if (postMergeData.byE164 == null && e164 != null && (changeSelf || notSelf(e164, pni, aci))) { breadCrumbs += "FinalUpdateE164" operations += PnpOperation.SetE164( recipientId = primaryId, @@ -2694,7 +2677,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - if (finalData.byPniSid == null && finalData.byPniOnly == null && pni != null) { + if (postMergeData.byPni == null && pni != null) { breadCrumbs += "FinalUpdatePni" operations += PnpOperation.SetPni( recipientId = primaryId, @@ -2702,12 +2685,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - if (!pniVerified && fullData.pniSidRecord != null && finalData.aciSidRecord != null && sessions.hasAnySessionFor(fullData.pniSidRecord.serviceId.toString())) { + val preSseOperationCount = operations.size + + operations += sessionSwitchoverEventIfNeeded(pniVerified, preMergeData.e164Record, postMergeData.e164Record) + operations += sessionSwitchoverEventIfNeeded(pniVerified, preMergeData.pniRecord, postMergeData.pniRecord) + operations += sessionSwitchoverEventIfNeeded(pniVerified, preMergeData.aciRecord, postMergeData.aciRecord) + + if (operations.size > preSseOperationCount) { breadCrumbs += "FinalUpdateSSE" - operations += PnpOperation.SessionSwitchoverInsert( - recipientId = primaryId, - e164 = finalData.e164 - ) } return PnpChangeSet( @@ -2717,12 +2702,51 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } + /** + * A session switchover event indicates a situation where we start communicating with a different session that we were before. + * If a switchover is "verified" (i.e. proven safe cryptographically by the sender), then this doesn't require a user-visible event. + * But if it's not verified and we're switching from one established session to another, the user needs to be aware. + */ + private fun needsSessionSwitchoverEvent(pniVerified: Boolean, oldServiceId: ServiceId?, newServiceId: ServiceId?): Boolean { + return !pniVerified && + oldServiceId != null && + newServiceId != null && + oldServiceId != newServiceId && + sessions.hasAnySessionFor(oldServiceId.toString()) && + identities.getIdentityStoreRecord(oldServiceId)?.identityKey != identities.getIdentityStoreRecord(newServiceId)?.identityKey + } + + /** + * For details on SSE's, see [needsSessionSwitchoverEvent]. This method is just a helper around comparing service ID's from two + * records and turning it into a possible event. + */ + private fun sessionSwitchoverEventIfNeeded(pniVerified: Boolean, oldRecord: RecipientRecord?, newRecord: RecipientRecord?): List { + return if (oldRecord != null && newRecord != null && needsSessionSwitchoverEvent(pniVerified, oldRecord.serviceId, newRecord.serviceId)) { + listOf( + PnpOperation.SessionSwitchoverInsert( + recipientId = newRecord.id, + e164 = newRecord.e164 + ) + ) + } else { + emptyList() + } + } + + private fun notSelf(data: PnpDataSet): Boolean { + return notSelf(data.e164, data.pni, data.aci) + } + private fun notSelf(e164: String?, pni: PNI?, aci: ACI?): Boolean { return (e164 == null || e164 != SignalStore.account().e164) && (pni == null || pni != SignalStore.account().pni) && (aci == null || aci != SignalStore.account().aci) } + private fun isSelf(data: PnpDataSet): Boolean { + return isSelf(data.e164, data.pni, data.aci) + } + private fun isSelf(e164: String?, pni: PNI?, aci: ACI?): Boolean { return (e164 != null && e164 == SignalStore.account().e164) || (pni != null && pni == SignalStore.account().pni) || @@ -2735,7 +2759,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val operations: LinkedHashSet = linkedSetOf() // This is a special case. The ACI passed in doesn't match the common record. We can't change ACIs, so we need to make a new record. - if (aci != null && aci != record.serviceId && record.serviceId != null && !record.sidIsPni()) { + if (aci != null && aci != record.aci && record.aci != null) { breadCrumbs += "AciDoesNotMatchCommonRecord" if (record.e164 == e164 && (changeSelf || notSelf(e164, pni, aci))) { @@ -2773,7 +2797,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - if (aci != null && record.serviceId != aci) { + if (aci != null && record.aci != aci) { operations += PnpOperation.SetAci( recipientId = commonId, aci = aci @@ -2788,9 +2812,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - val newServiceId: ServiceId? = aci ?: pni ?: record.serviceId + val oldServiceId: ServiceId? = record.aci ?: record.pni + val newServiceId: ServiceId? = aci ?: pni ?: oldServiceId - if (!pniVerified && record.serviceId != null && record.serviceId != newServiceId && sessions.hasAnySessionFor(record.serviceId.toString())) { + if (!pniVerified && newServiceId != oldServiceId && oldServiceId != null && sessions.hasAnySessionFor(oldServiceId.toString())) { operations += PnpOperation.SessionSwitchoverInsert(recipientId = commonId, e164 = record.e164 ?: e164) } @@ -2801,133 +2826,180 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } - private fun processPossibleE164PniSidMerge(pni: PNI?, pniVerified: Boolean, data: PnpDataSet, breadCrumbs: MutableList): LinkedHashSet { - if (pni == null || data.byE164 == null || data.byPniSid == null || data.e164Record == null || data.pniSidRecord == null || data.e164Record.id == data.pniSidRecord.id) { + /** + * Resolves any possible E164-PNI conflicts/merges. In these situations, the E164-based row is more dominant + * and can "steal" data from PNI-based rows, or merge PNI-based rows into itself. + * + * We do have to be careful when merging/stealing data to leave possible ACI's that could be on the PNI + * row alone: remember, ACI's are forever-bound to a given RecipientId. + */ + private fun processPossibleE164PniMerge(data: PnpDataSet, pniVerified: Boolean, changeSelf: Boolean, breadCrumbs: MutableList): LinkedHashSet { + // Filter to ensure that we're only looking at situations where a PNI and E164 record both exist but do not match + if (data.pni == null || data.byPni == null || data.pniRecord == null || data.e164 == null || data.byE164 == null || data.e164Record == null || data.e164Record.id == data.pniRecord.id) { return linkedSetOf() } // We have found records for both the E164 and PNI, and they're different - breadCrumbs += "E164PniSidMerge" + breadCrumbs += "E164PniMerge" + + if (!changeSelf && isSelf(data)) { + breadCrumbs += "ChangeSelfPreventsE164PniMerge" + return linkedSetOf() + } val operations: LinkedHashSet = linkedSetOf() - // The PNI record only has a single identifier. We know we must merge. - if (data.pniSidRecord.sidOnly(pni)) { + if (data.pniRecord.pniOnly()) { + // The PNI record only has a single identifier. We know we must merge. breadCrumbs += "PniOnly" if (data.e164Record.pni != null) { + // The e164 record we're merging into has a PNI already. This means that we've entered an 'unstable PNI mapping' scenario. + // This isn't expected, but we need to handle it gracefully and merge the two rows together. operations += PnpOperation.RemovePni(data.byE164) + + if (needsSessionSwitchoverEvent(pniVerified, data.e164Record.pni, data.pni)) { + breadCrumbs += "E164IdentityMismatchesPniIdentity" + operations += PnpOperation.SessionSwitchoverInsert(data.byE164, data.e164) + } } operations += PnpOperation.Merge( primaryId = data.byE164, - secondaryId = data.byPniSid + secondaryId = data.byPni ) - - // TODO: Possible session switchover? } else { - check(!data.pniSidRecord.pniAndAci() && data.pniSidRecord.e164 != null) + // The record we're taking data from also has either an ACI or e164, so we need to leave that data behind - breadCrumbs += "PniSidRecordHasE164" + breadCrumbs += if (data.pniRecord.aci != null && data.pniRecord.e164 != null) { + "PniRecordHasE164AndAci" + } else if (data.pniRecord.aci != null) { + "PniRecordHasAci" + } else { + "PniRecordHasE164" + } - operations += PnpOperation.RemovePni(data.byPniSid) + // Move the PNI from the PNI record to the e164 record + operations += PnpOperation.RemovePni(data.byPni) operations += PnpOperation.SetPni( recipientId = data.byE164, - pni = pni + pni = data.pni ) - if (!pniVerified && sessions.hasAnySessionFor(data.pniSidRecord.serviceId.toString())) { - operations += PnpOperation.SessionSwitchoverInsert(recipientId = data.byPniSid, e164 = data.pniSidRecord.e164) + // By migrating the PNI to the e164 record, we may cause an SSE + if (needsSessionSwitchoverEvent(pniVerified, data.e164Record.serviceId, data.e164Record.aci ?: data.pni)) { + operations += PnpOperation.SessionSwitchoverInsert(recipientId = data.byE164, e164 = data.e164Record.e164) + } + + // This is a defensive move where we put an SSE in the session we stole the PNI from and where we're moving it to in order + // to avoid a multi-step PNI swap. You could imagine that we might remove the PNI in this function call, but then add one back + // in the next function call, and each step on it's own would think that no SSE is necessary. Given that this scenario only + // happens with an unstable PNI-E164 mapping, we get out ahead of it by putting an SSE in both preemptively. + if (!pniVerified && data.pniRecord.aci == null && sessions.hasAnySessionFor(data.pni.toString())) { + operations += PnpOperation.SessionSwitchoverInsert(recipientId = data.byPni, e164 = data.pniRecord.e164) - if (data.e164Record.serviceId == null || data.e164Record.sidIsPni()) { + if (data.e164Record.aci == null) { operations += PnpOperation.SessionSwitchoverInsert(recipientId = data.byE164, e164 = data.e164Record.e164) } } - - if (!pniVerified && data.e164Record.serviceId != null && data.e164Record.sidIsPni() && sessions.hasAnySessionFor(data.e164Record.serviceId.toString())) { - operations += PnpOperation.SessionSwitchoverInsert(recipientId = data.byE164, e164 = data.e164) - } } return operations } - private fun processPossiblePniSidAciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet, changeSelf: Boolean, breadCrumbs: MutableList): LinkedHashSet { - if (pni == null || aci == null || data.byPniSid == null || data.byAciSid == null || data.pniSidRecord == null || data.aciSidRecord == null || data.pniSidRecord.id == data.aciSidRecord.id) { + /** + * Resolves any possible PNI-ACI conflicts/merges. In these situations, the ACI-based row is more dominant + * and can "steal" data from PNI-based rows, or merge PNI-based rows into itself. + */ + private fun processPossiblePniAciMerge(data: PnpDataSet, pniVerified: Boolean, changeSelf: Boolean, breadCrumbs: MutableList): LinkedHashSet { + // Filter to ensure that we're only looking at situations where a PNI and ACI record both exist but do not match + if (data.pni == null || data.byPni == null || data.pniRecord == null || data.aci == null || data.byAci == null || data.aciRecord == null || data.pniRecord.id == data.aciRecord.id) { return linkedSetOf() } - if (!changeSelf && isSelf(e164, pni, aci)) { - breadCrumbs += "ChangeSelfPreventsPniSidAciSidMerge" + // We have found records for both the PNI and ACI, and they're different + breadCrumbs += "PniAciMerge" + + if (!changeSelf && isSelf(data)) { + breadCrumbs += "ChangeSelfPreventsPniAciMerge" return linkedSetOf() } - // We have found records for both the PNI and ACI, and they're different - breadCrumbs += "PniSidAciSidMerge" - val operations: LinkedHashSet = linkedSetOf() // The PNI record only has a single identifier. We know we must merge. - if (data.pniSidRecord.sidOnly(pni)) { + if (data.pniRecord.pniOnly()) { breadCrumbs += "PniOnly" - if (data.aciSidRecord.pni != null) { - operations += PnpOperation.RemovePni(data.byAciSid) + if (data.aciRecord.pni != null) { + operations += PnpOperation.RemovePni(data.byAci) } operations += PnpOperation.Merge( - primaryId = data.byAciSid, - secondaryId = data.byPniSid + primaryId = data.byAci, + secondaryId = data.byPni ) - } else if (data.pniSidRecord.e164 == e164) { - // The PNI record also has the E164 on it. We're going to be stealing both fields, + } else if (data.pniRecord.aci == null && data.pniRecord.e164 == data.e164) { + // The PNI record also has the E164 on it with no ACI. We're going to be stealing all of it's fields, // so this is basically a merge with a little bit of extra prep. - breadCrumbs += "PniSidRecordHasMatchingE164" + breadCrumbs += "PniRecordHasMatchingE164AndNoAci" - if (data.aciSidRecord.pni != null) { - operations += PnpOperation.RemovePni(data.byAciSid) + if (data.aciRecord.pni != null) { + operations += PnpOperation.RemovePni(data.byAci) } - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164) { - operations += PnpOperation.RemoveE164(data.byAciSid) + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164) { + operations += PnpOperation.RemoveE164(data.byAci) + + // This also becomes a change number event + if (notSelf(data) && !data.aciRecord.isBlocked) { + operations += PnpOperation.ChangeNumberInsert( + recipientId = data.byAci, + oldE164 = data.aciRecord.e164, + newE164 = data.e164!! + ) + } } operations += PnpOperation.Merge( - primaryId = data.byAciSid, - secondaryId = data.byPniSid + primaryId = data.byAci, + secondaryId = data.byPni ) + } else { + // The PNI record either has an ACI or a non-matching e164, meaning we need to steal what we need and leave the rest behind - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164 && notSelf(e164, pni, aci) && !data.aciSidRecord.isBlocked) { - operations += PnpOperation.ChangeNumberInsert( - recipientId = data.byAciSid, - oldE164 = data.aciSidRecord.e164, - newE164 = e164!! - ) + breadCrumbs += if (data.pniRecord.aci != null && data.pniRecord.e164 != data.e164) { + "PniRecordHasAciAndNonMatchingE164" + } else if (data.pniRecord.aci != null) { + "PniRecordHasAci" + } else { + "PniRecordHasNonMatchingE164" } - } else { - check(data.pniSidRecord.e164 != null && data.pniSidRecord.e164 != e164) - breadCrumbs += "PniSidRecordHasNonMatchingE164" - operations += PnpOperation.RemovePni(data.byPniSid) + operations += PnpOperation.RemovePni(data.byPni) - if (data.aciSidRecord.pni != pni) { - operations += PnpOperation.SetPni( - recipientId = data.byAciSid, - pni = pni - ) - } + operations += PnpOperation.SetPni( + recipientId = data.byAci, + pni = data.pni + ) + + if (data.e164 != null && data.aciRecord.e164 != data.e164) { + if (data.pniRecord.e164 == data.e164) { + operations += PnpOperation.RemoveE164( + recipientId = data.byPni + ) + } - if (e164 != null && data.aciSidRecord.e164 != e164) { operations += PnpOperation.SetE164( - recipientId = data.byAciSid, - e164 = e164 + recipientId = data.byAci, + e164 = data.e164 ) - if (data.aciSidRecord.e164 != null && notSelf(e164, pni, aci) && !data.aciSidRecord.isBlocked) { + if (data.aciRecord.e164 != null && notSelf(data) && !data.aciRecord.isBlocked) { operations += PnpOperation.ChangeNumberInsert( - recipientId = data.byAciSid, - oldE164 = data.aciSidRecord.e164, - newE164 = e164 + recipientId = data.byAci, + oldE164 = data.aciRecord.e164, + newE164 = data.e164 ) } } @@ -2936,82 +3008,87 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return operations } - private fun processPossibleE164AciSidMerge(e164: String?, pni: PNI?, aci: ACI?, data: PnpDataSet, changeSelf: Boolean, breadCrumbs: MutableList): List { - if (e164 == null || aci == null || data.byE164 == null || data.byAciSid == null || data.e164Record == null || data.aciSidRecord == null || data.e164Record.id == data.aciSidRecord.id) { + /** + * Resolves any possible E164-ACI conflicts/merges. In these situations, the ACI-based row is more dominant + * and can "steal" data from E164-based rows, or merge E164-based rows into itself. + */ + private fun processPossibleE164AciMerge(data: PnpDataSet, pniVerified: Boolean, changeSelf: Boolean, breadCrumbs: MutableList): List { + // Filter to ensure that we're only looking at situations where a E164 and ACI record both exist but do not match + if (data.e164 == null || data.byE164 == null || data.e164Record == null || data.aci == null || data.byAci == null || data.aciRecord == null || data.e164Record.id == data.aciRecord.id) { return emptyList() } - if (!changeSelf && isSelf(e164, pni, aci)) { - breadCrumbs += "ChangeSelfPreventsE164AciSidMerge" + // We have found records for both the E164 and ACI, and they're different + breadCrumbs += "E164AciMerge" + + if (!changeSelf && isSelf(data)) { + breadCrumbs += "ChangeSelfPreventsE164AciMerge" return emptyList() } - // We have found records for both the E164 and ACI, and they're different - breadCrumbs += "E164AciSidMerge" - val operations: MutableList = mutableListOf() // The E164 record only has a single identifier. We know we must merge. if (data.e164Record.e164Only()) { breadCrumbs += "E164Only" - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164) { - operations += PnpOperation.RemoveE164(data.byAciSid) + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164) { + operations += PnpOperation.RemoveE164(data.byAci) } operations += PnpOperation.Merge( - primaryId = data.byAciSid, + primaryId = data.byAci, secondaryId = data.byE164 ) - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164 && notSelf(e164, pni, aci) && !data.aciSidRecord.isBlocked) { + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164 && notSelf(data) && !data.aciRecord.isBlocked) { operations += PnpOperation.ChangeNumberInsert( - recipientId = data.byAciSid, - oldE164 = data.aciSidRecord.e164, - newE164 = e164 + recipientId = data.byAci, + oldE164 = data.aciRecord.e164, + newE164 = data.e164 ) } - } else if (data.e164Record.pni != null && data.e164Record.pni == pni) { + } else if (data.e164Record.pni != null && data.e164Record.pni == data.pni) { // The E164 record also has the PNI on it. We're going to be stealing both fields, // so this is basically a merge with a little bit of extra prep. breadCrumbs += "E164RecordHasMatchingPni" - if (data.aciSidRecord.pni != null) { - operations += PnpOperation.RemovePni(data.byAciSid) + if (data.aciRecord.pni != null) { + operations += PnpOperation.RemovePni(data.byAci) } - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164) { - operations += PnpOperation.RemoveE164(data.byAciSid) + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164) { + operations += PnpOperation.RemoveE164(data.byAci) } operations += PnpOperation.Merge( - primaryId = data.byAciSid, + primaryId = data.byAci, secondaryId = data.byE164 ) - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164 && notSelf(e164, pni, aci) && !data.aciSidRecord.isBlocked) { + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164 && notSelf(data) && !data.aciRecord.isBlocked) { operations += PnpOperation.ChangeNumberInsert( - recipientId = data.byAciSid, - oldE164 = data.aciSidRecord.e164, - newE164 = e164 + recipientId = data.byAci, + oldE164 = data.aciRecord.e164, + newE164 = data.e164 ) } } else { - check(data.e164Record.pni == null || data.e164Record.pni != pni) + check(data.e164Record.pni == null || data.e164Record.pni != data.pni) breadCrumbs += "E164RecordHasNonMatchingPni" operations += PnpOperation.RemoveE164(data.byE164) operations += PnpOperation.SetE164( - recipientId = data.byAciSid, - e164 = e164 + recipientId = data.byAci, + e164 = data.e164 ) - if (data.aciSidRecord.e164 != null && data.aciSidRecord.e164 != e164 && notSelf(e164, pni, aci) && !data.aciSidRecord.isBlocked) { + if (data.aciRecord.e164 != null && data.aciRecord.e164 != data.e164 && notSelf(data) && !data.aciRecord.isBlocked) { operations += PnpOperation.ChangeNumberInsert( - recipientId = data.byAciSid, - oldE164 = data.aciSidRecord.e164, - newE164 = e164 + recipientId = data.byAci, + oldE164 = data.aciRecord.e164, + newE164 = data.e164 ) } } @@ -3047,7 +3124,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return readableDatabase .select(ID) .from(TABLE_NAME) - .where("$REGISTERED = ? and $HIDDEN = ? AND $SERVICE_ID NOT NULL", 1, 0) + .where("$REGISTERED = ? and $HIDDEN = ? AND $ACI_COLUMN NOT NULL", 1, 0) .run() .readToSet { cursor -> RecipientId.from(cursor.requireLong(ID)) @@ -3151,7 +3228,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return readableDatabase .select() .from(TABLE_NAME) - .where("$ID LIKE ? OR $SERVICE_ID LIKE ? OR $PNI_COLUMN LIKE ?", "%$query%", "%$query%", "%$query%") + .where("$ID LIKE ? OR $ACI_COLUMN LIKE ? OR $PNI_COLUMN LIKE ?", "%$query%", "%$query%", "%$query%") .run() .readToList { cursor -> getRecord(context, cursor) @@ -3482,7 +3559,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } for (uuid in blockedUuid) { - db.update(TABLE_NAME, setBlocked, "$SERVICE_ID = ?", arrayOf(uuid)) + db.update(TABLE_NAME, setBlocked, "$ACI_COLUMN = ?", arrayOf(uuid)) } val groupIdStrings: MutableList = ArrayList(groupIds.size) @@ -3810,7 +3887,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val uuidValues = contentValuesOf( PHONE to (secondaryRecord.e164 ?: primaryRecord.e164), - SERVICE_ID to (primaryRecord.serviceId ?: secondaryRecord.serviceId)?.toString(), + ACI_COLUMN to (primaryRecord.aci ?: secondaryRecord.aci)?.toString(), PNI_COLUMN to (newPni ?: secondaryRecord.pni ?: primaryRecord.pni)?.toString(), BLOCKED to (secondaryRecord.isBlocked || primaryRecord.isBlocked), MESSAGE_RINGTONE to Optional.ofNullable(primaryRecord.messageRingtone).or(Optional.ofNullable(secondaryRecord.messageRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null), @@ -3862,13 +3939,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da private fun buildContentValuesForNewUser(e164: String?, pni: PNI?, aci: ACI?): ContentValues { check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" } - val serviceId = (aci ?: pni)?.toString() val values = contentValuesOf( PHONE to e164, - SERVICE_ID to serviceId, + ACI_COLUMN to aci?.toString(), PNI_COLUMN to pni?.toString(), STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey()), - AVATAR_COLOR to AvatarColorHash.forAddress(serviceId, e164).serialize() + AVATAR_COLOR to AvatarColorHash.forAddress((aci ?: pni)?.toString(), e164).serialize() ) if (pni != null || aci != null) { @@ -3885,8 +3961,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val systemName = ProfileName.fromParts(contact.systemGivenName.orElse(null), contact.systemFamilyName.orElse(null)) val username = contact.username.orElse(null) - if (contact.serviceId.isValid) { - put(SERVICE_ID, contact.serviceId.toString()) + if (contact.aci.isValid) { + put(ACI_COLUMN, contact.aci.toString()) } if (FeatureFlags.phoneNumberPrivacy()) { @@ -3918,14 +3994,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp) if (contact.unregisteredTimestamp > 0L) { put(REGISTERED, RegisteredState.NOT_REGISTERED.id) - } else if (contact.serviceId.isValid) { + } else if (contact.aci.isValid) { put(REGISTERED, RegisteredState.REGISTERED.id) } else { Log.w(TAG, "Contact is marked as registered, but has no serviceId! Can't locally mark registered. (Phone: ${contact.number.orElse("null")}, Username: ${username?.isNotEmpty()})") } if (isInsert) { - put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.serviceId.toString(), contact.number.orNull()).serialize()) + put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.aci.toString(), contact.number.orNull()).serialize()) } } } @@ -4006,7 +4082,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da writableDatabase .update(TABLE_NAME) .values( - SERVICE_ID to null, + ACI_COLUMN to null, PNI_COLUMN to null ) .run { @@ -4081,7 +4157,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da writableDatabase.execSQL( """ UPDATE $TABLE_NAME - SET $SERVICE_ID = $PNI_COLUMN + SET $ACI_COLUMN = $PNI_COLUMN WHERE $ID = ? AND $PNI_COLUMN NOT NULL """, SqlUtil.buildArgs(recipientId) @@ -4156,7 +4232,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return RecipientRecord( id = recipientId, - serviceId = ServiceId.parseOrNull(cursor.requireString(SERVICE_ID)), + aci = ACI.parseOrNull(cursor.requireString(ACI_COLUMN)), pni = PNI.parseOrNull(cursor.requireString(PNI_COLUMN)), username = cursor.requireString(USERNAME), e164 = cursor.requireString(PHONE), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt index 2fd3c1e266..2fdc940a97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt @@ -116,12 +116,20 @@ class SenderKeySharedTable internal constructor(context: Context?, databaseHelpe fun deleteAllFor(recipientId: RecipientId) { val recipient = Recipient.resolved(recipientId) if (recipient.hasServiceId()) { - writableDatabase - .delete(TABLE_NAME) - .where("$ADDRESS = ?", recipient.requireServiceId().toString()) - .run() + if (recipient.hasAci()) { + writableDatabase + .delete(TABLE_NAME) + .where("$ADDRESS = ?", recipient.requireAci().toString()) + .run() + } + if (recipient.hasPni()) { + writableDatabase + .delete(TABLE_NAME) + .where("$ADDRESS = ?", recipient.requirePni().toString()) + .run() + } } else { - Log.w(TAG, "Recipient doesn't have a UUID! $recipientId") + Log.w(TAG, "Recipient doesn't have a ServiceId! $recipientId") } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index 4068485e5f..98a9136bcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -63,7 +63,6 @@ import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject import org.thoughtcrime.securesms.util.LRUCache import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.isScheduled -import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.storage.SignalAccountRecord import org.whispersystems.signalservice.api.storage.SignalAccountRecord.PinnedConversation import org.whispersystems.signalservice.api.storage.SignalContactRecord @@ -1700,7 +1699,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa if (threadRecipient.isPushV2Group) { val inviteAddState = record.gv2AddInviteState if (inviteAddState != null) { - val from = RecipientId.from(ServiceId.from(inviteAddState.addedOrInvitedBy)) + val from = RecipientId.from(inviteAddState.addedOrInvitedBy) return if (inviteAddState.isInvited) { Log.i(TAG, "GV2 invite message request from $from") Extra.forGroupV2invite(from, authorId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 71b988c2ad..1007d5d261 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V196_BackCallLinksW import org.thoughtcrime.securesms.database.helpers.migration.V197_DropAvatarColorFromCallLinks import org.thoughtcrime.securesms.database.helpers.migration.V198_AddMacDigestColumn import org.thoughtcrime.securesms.database.helpers.migration.V199_AddThreadActiveColumn +import org.thoughtcrime.securesms.database.helpers.migration.V200_ResetPniColumn /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. @@ -63,7 +64,7 @@ object SignalDatabaseMigrations { val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass) - const val DATABASE_VERSION = 199 + const val DATABASE_VERSION = 200 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -270,6 +271,10 @@ object SignalDatabaseMigrations { if (oldVersion < 199) { V199_AddThreadActiveColumn.migrate(context, db, oldVersion, newVersion) } + + if (oldVersion < 200) { + V200_ResetPniColumn.migrate(context, db, oldVersion, newVersion) + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt index 75de5ac4de..d4ef1d7654 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt @@ -47,8 +47,8 @@ import org.thoughtcrime.securesms.util.FileUtils import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.Triple import org.thoughtcrime.securesms.util.Util -import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.DistributionId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.io.File import java.util.LinkedList import java.util.Locale diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V185_MessageRecipientsAndEditMessageMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V185_MessageRecipientsAndEditMessageMigration.kt index b55bf2b411..7c493a9bbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V185_MessageRecipientsAndEditMessageMigration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V185_MessageRecipientsAndEditMessageMigration.kt @@ -16,7 +16,7 @@ import org.signal.core.util.requireString import org.thoughtcrime.securesms.database.KeyValueDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI /** * This is a combination of the edit message and message recipient migrations (would have been V185 and v186), but as they diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V188_FixMessageRecipientsAndEditMessageMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V188_FixMessageRecipientsAndEditMessageMigration.kt index ba6f3a101b..ff9bb0d7e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V188_FixMessageRecipientsAndEditMessageMigration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V188_FixMessageRecipientsAndEditMessageMigration.kt @@ -16,7 +16,7 @@ import org.signal.core.util.requireString import org.thoughtcrime.securesms.database.KeyValueDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI /** * This is a fix for a bad situation that could happen during [V185_MessageRecipientsAndEditMessageMigration]. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V200_ResetPniColumn.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V200_ResetPniColumn.kt new file mode 100644 index 0000000000..aa4d5ed736 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V200_ResetPniColumn.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase + +/** + * This updates the PNI column to have the proper serialized format. + */ +@Suppress("ClassName") +object V200_ResetPniColumn : SignalDatabaseMigration { + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("UPDATE recipient SET pni = 'PNI:' || pni WHERE pni NOT NULL") + db.execSQL("ALTER TABLE recipient RENAME COLUMN uuid to aci") + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupCallUpdateMessageFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupCallUpdateMessageFactory.java index 44abf17c84..7be56d8053 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupCallUpdateMessageFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupCallUpdateMessageFactory.java @@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.DateUtils; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.ArrayList; @@ -31,7 +31,7 @@ public class GroupCallUpdateMessageFactory implements UpdateDescription.Spannabl private final ACI selfAci; public GroupCallUpdateMessageFactory(@NonNull Context context, - @NonNull List joinedMembers, + @NonNull List joinedMembers, boolean withTime, @NonNull GroupCallUpdateDetails groupCallUpdateDetails) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt index b99a381a50..3113866d89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt @@ -176,7 +176,7 @@ class GroupRecord( if (isV2Group) { val serviceId = recipient.serviceId if (serviceId.isPresent) { - return DecryptedGroupUtil.findPendingByUuid(requireV2GroupProperties().decryptedGroup.pendingMembersList, serviceId.get().uuid()) + return DecryptedGroupUtil.findPendingByUuid(requireV2GroupProperties().decryptedGroup.pendingMembersList, serviceId.get().rawUuid) .isPresent } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index 9ac330c9e8..684d3bb3a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.SpanUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -72,9 +72,9 @@ final class GroupsV2UpdateMessageProducer { * When the revision of the group is 0, the change is very noisy and only the editor is useful. */ UpdateDescription describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange decryptedGroupChange) { - Optional selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getAci().uuid()); + Optional selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getAci().getRawUuid()); if (!selfPending.isPresent() && selfIds.getPni() != null) { - selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getPni().uuid()); + selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getPni().getRawUuid()); } if (selfPending.isPresent()) { @@ -90,8 +90,8 @@ UpdateDescription describeNewGroup(@NonNull DecryptedGroup group, @NonNull Decry } } - if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getAci().uuid()).isPresent() || - (selfIds.getPni() != null && DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getPni().uuid()).isPresent())) + if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getAci().getRawUuid()).isPresent() || + (selfIds.getPni() != null && DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getPni().getRawUuid()).isPresent())) { return updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16); } else { @@ -820,11 +820,11 @@ private UpdateDescription updateDescription(@StringRes int stringRes, @NonNull ByteString uuid1Bytes, @DrawableRes int iconResource) { - ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(serviceId); + ACI aci = ACI.parseOrUnknown(uuid1Bytes); + RecipientId recipientId = RecipientId.from(aci); return UpdateDescription.mentioning( - Collections.singletonList(serviceId), + Collections.singletonList(aci), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null)); @@ -839,14 +839,14 @@ private UpdateDescription updateDescription(@StringRes int stringRes, @NonNull ByteString uuid2Bytes, @DrawableRes int iconResource) { - ServiceId sid1 = ServiceId.fromByteStringOrUnknown(uuid1Bytes); - ServiceId sid2 = ServiceId.fromByteStringOrUnknown(uuid2Bytes); + ACI aci1 = ACI.parseOrUnknown(uuid1Bytes); + ACI aci2 = ACI.parseOrUnknown(uuid2Bytes); - RecipientId recipientId1 = RecipientId.from(sid1); - RecipientId recipientId2 = RecipientId.from(sid2); + RecipientId recipientId1 = RecipientId.from(aci1); + RecipientId recipientId2 = RecipientId.from(aci2); return UpdateDescription.mentioning( - Arrays.asList(sid1, sid2), + Arrays.asList(aci1, aci2), () -> { List recipientIdList = Arrays.asList(recipientId1, recipientId2); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null)); @@ -862,11 +862,11 @@ private UpdateDescription updateDescription(@StringRes int stringRes, @NonNull Object formatArg, @DrawableRes int iconResource) { - ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(serviceId); + ACI aci = ACI.parseOrUnknown(uuid1Bytes); + RecipientId recipientId = RecipientId.from(aci); return UpdateDescription.mentioning( - Collections.singletonList(serviceId), + Collections.singletonList(aci), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, Collections.singletonList(formatArg))); @@ -883,11 +883,11 @@ private UpdateDescription updateDescription(@PluralsRes int stringRes, @NonNull Object formatArg, @DrawableRes int iconResource) { - ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(serviceId); + ACI aci = ACI.parseOrUnknown(uuid1Bytes); + RecipientId recipientId = RecipientId.from(aci); return UpdateDescription.mentioning( - Collections.singletonList(serviceId), + Collections.singletonList(aci), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getResources().getQuantityString(stringRes, quantity, makePlaceholders(recipientIdList, Collections.singletonList(formatArg))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 635f2f3228..b6d3fa4964 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -307,7 +308,7 @@ public boolean isSelfCreatedGroup() { private static boolean selfCreatedGroup(@NonNull DecryptedGroupChange change) { return change.getRevision() == 0 && - change.getEditor().equals(UuidUtil.toByteString(SignalStore.account().requireAci().uuid())); + change.getEditor().equals(UuidUtil.toByteString(SignalStore.account().requireAci().getRawUuid())); } public static @NonNull UpdateDescription getGv2ChangeDescription(@NonNull Context context, @NonNull String body, @Nullable Consumer recipientClickHandler) { @@ -345,13 +346,13 @@ private static boolean selfCreatedGroup(@NonNull DecryptedGroupChange change) { } DecryptedGroup groupState = decryptedGroupV2Context.getGroupState(); - boolean invited = DecryptedGroupUtil.findPendingByUuid(groupState.getPendingMembersList(), SignalStore.account().requireAci().uuid()).isPresent(); + boolean invited = DecryptedGroupUtil.findPendingByUuid(groupState.getPendingMembersList(), SignalStore.account().requireAci().getRawUuid()).isPresent(); if (decryptedGroupV2Context.hasChange()) { UUID changeEditor = UuidUtil.fromByteStringOrNull(decryptedGroupV2Context.getChange().getEditor()); if (changeEditor != null) { - return new InviteAddState(invited, changeEditor); + return new InviteAddState(invited, ACI.from(changeEditor)); } } @@ -367,7 +368,7 @@ private static boolean selfCreatedGroup(@NonNull DecryptedGroupChange change) { @NonNull Function stringGenerator, @DrawableRes int iconResource) { - return UpdateDescription.mentioning(Collections.singletonList(recipient.getServiceId().orElse(ServiceId.UNKNOWN)), + return UpdateDescription.mentioning(Collections.singletonList(recipient.getAci().orElse(ACI.UNKNOWN)), () -> new SpannableString(stringGenerator.apply(recipient.resolve())), iconResource); } @@ -435,10 +436,10 @@ private UpdateDescription getGroupMigrationEventDescription(@NonNull Context con public static @NonNull UpdateDescription getGroupCallUpdateDescription(@NonNull Context context, @NonNull String body, boolean withTime) { GroupCallUpdateDetails groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(body); - List joinedMembers = Stream.of(groupCallUpdateDetails.getInCallUuidsList()) + List joinedMembers = Stream.of(groupCallUpdateDetails.getInCallUuidsList()) .map(UuidUtil::parseOrNull) .withoutNulls() - .map(ServiceId::from) + .map(ACI::from) .toList(); UpdateDescription.SpannableFactory stringFactory = new GroupCallUpdateMessageFactory(context, joinedMembers, withTime, groupCallUpdateDetails); @@ -467,7 +468,7 @@ public boolean isGroupV2JoinRequest(@Nullable ServiceId serviceId) { return false; } - return isGroupV2JoinRequest(UuidUtil.toByteString(serviceId.uuid())); + return isGroupV2JoinRequest(UuidUtil.toByteString(serviceId.getRawUuid())); } public boolean isGroupV2JoinRequest(@NonNull ByteString uuid) { @@ -489,7 +490,7 @@ public boolean isCollapsedGroupV2JoinUpdate(@Nullable ServiceId serviceId) { DecryptedGroupChange change = decryptedGroupV2Context.getChange(); return change.getNewRequestingMembersCount() > 0 && change.getDeleteRequestingMembersCount() > 0 && - (serviceId == null || change.getEditor().equals(UuidUtil.toByteString(serviceId.uuid()))); + (serviceId == null || change.getEditor().equals(UuidUtil.toByteString(serviceId.getRawUuid()))); } return false; } @@ -748,14 +749,14 @@ public int getRevisionNumber() { public static final class InviteAddState { private final boolean invited; - private final UUID addedOrInvitedBy; + private final ACI addedOrInvitedBy; - public InviteAddState(boolean invited, @NonNull UUID addedOrInvitedBy) { + public InviteAddState(boolean invited, @NonNull ACI addedOrInvitedBy) { this.invited = invited; this.addedOrInvitedBy = addedOrInvitedBy; } - public @NonNull UUID getAddedOrInvitedBy() { + public @NonNull ACI getAddedOrInvitedBy() { return addedOrInvitedBy; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt index 24091260fd..7cafb8cfda 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt @@ -19,8 +19,9 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId import org.thoughtcrime.securesms.wallpaper.ChatWallpaper -import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.util.Optional /** @@ -28,7 +29,7 @@ import java.util.Optional */ data class RecipientRecord( val id: RecipientId, - val serviceId: ServiceId?, + val aci: ACI?, val pni: PNI?, val username: String?, val e164: String?, @@ -89,21 +90,23 @@ data class RecipientRecord( } fun e164Only(): Boolean { - return this.e164 != null && this.serviceId == null + return this.e164 != null && this.aci == null } - fun sidOnly(sid: ServiceId): Boolean { - return this.e164 == null && this.serviceId == sid && (this.pni == null || this.pni == sid) + fun pniOnly(): Boolean { + return this.e164 == null && this.aci == null && this.pni != null } - fun sidIsPni(): Boolean { - return this.serviceId != null && this.pni != null && this.serviceId == this.pni + fun aciOnly(): Boolean { + return this.e164 == null && this.pni == null && this.aci != null } fun pniAndAci(): Boolean { - return this.serviceId != null && this.pni != null && this.serviceId != this.pni + return this.aci != null && this.pni != null } + val serviceId: ServiceId? = this.aci ?: this.pni + /** * A bundle of data that's only necessary when syncing to storage service, not for a * [Recipient]. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java index 5f8e11c864..9eaaefb4ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java @@ -11,6 +11,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.Collection; @@ -18,6 +19,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Contains a list of people mentioned in an update message and a function to create the update message. @@ -28,14 +30,14 @@ public interface SpannableFactory { Spannable create(); } - private final Collection mentioned; - private final SpannableFactory stringFactory; - private final Spannable staticString; - private final int lightIconResource; - private final int lightTint; - private final int darkTint; + private final Collection mentioned; + private final SpannableFactory stringFactory; + private final Spannable staticString; + private final int lightIconResource; + private final int lightTint; + private final int darkTint; - private UpdateDescription(@NonNull Collection mentioned, + private UpdateDescription(@NonNull Collection mentioned, @Nullable SpannableFactory stringFactory, @Nullable Spannable staticString, @DrawableRes int iconResource, @@ -60,11 +62,11 @@ private UpdateDescription(@NonNull Collection mentioned, * @param mentioned UUIDs of recipients that are mentioned in the string. * @param stringFactory The background method for generating the string. */ - public static UpdateDescription mentioning(@NonNull Collection mentioned, + public static UpdateDescription mentioning(@NonNull Collection mentioned, @NonNull SpannableFactory stringFactory, @DrawableRes int iconResource) { - return new UpdateDescription(ServiceId.filterKnown(mentioned), + return new UpdateDescription(mentioned.stream().filter(ACI::isValid).collect(Collectors.toList()), stringFactory, null, iconResource, @@ -125,7 +127,7 @@ public boolean isStringStatic() { } @AnyThread - public @NonNull Collection getMentioned() { + public @NonNull Collection getMentioned() { return mentioned; } @@ -156,7 +158,7 @@ public static UpdateDescription concatWithNewLines(@NonNull List allMentioned = new HashSet<>(); + Set allMentioned = new HashSet<>(); for (UpdateDescription updateDescription : updateDescriptions) { allMentioned.addAll(updateDescription.getMentioned()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 6a6b908f68..f905e68981 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -85,8 +85,8 @@ import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.services.CallLinksService; import org.whispersystems.signalservice.api.services.DonationsService; import org.whispersystems.signalservice.api.services.ProfileService; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 1c8284bb7c..1a80182132 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -319,13 +319,13 @@ public static void ban(@NonNull Context context, GroupTable.V2GroupProperties groupProperties = SignalDatabase.groups().requireGroup(groupId).requireV2GroupProperties(); Recipient recipient = Recipient.resolved(recipientId); - if (groupProperties.getBannedMembers().contains(recipient.requireServiceId().uuid())) { + if (groupProperties.getBannedMembers().contains(recipient.requireServiceId().getRawUuid())) { Log.i(TAG, "Attempt to ban already banned recipient: " + recipientId); return; } try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.ban(recipient.requireServiceId().uuid()); + editor.ban(recipient.requireServiceId().getRawUuid()); } } @@ -336,7 +336,7 @@ public static void unban(@NonNull Context context, throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException { try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().uuid())); + editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().getRawUuid())); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 5525d3f571..6a113dafcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -64,8 +64,8 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException; import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; @@ -176,7 +176,7 @@ final class GroupManagerV2 { Map uuidCipherTexts = new HashMap<>(); for (Recipient recipient : recipients) { - uuidCipherTexts.put(recipient.requireServiceId().uuid(), clientZkGroupCipher.encryptUuid(recipient.requireServiceId().uuid())); + uuidCipherTexts.put(recipient.requireServiceId().getRawUuid(), clientZkGroupCipher.encryptUuid(recipient.requireServiceId().getRawUuid())); } return uuidCipherTexts; @@ -231,7 +231,7 @@ void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey return latest; } - Optional selfInFullMemberList = DecryptedGroupUtil.findMemberByUuid(latest.getMembersList(), selfAci.uuid()); + Optional selfInFullMemberList = DecryptedGroupUtil.findMemberByUuid(latest.getMembersList(), selfAci.getRawUuid()); if (!selfInFullMemberList.isPresent()) { return latest; @@ -356,7 +356,7 @@ final class GroupEditor extends LockOwner { groupCandidates = GroupCandidate.withoutExpiringProfileKeyCredentials(groupCandidates); } - return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid())); + return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.getRawUuid())); } @WorkerThread @@ -431,7 +431,7 @@ final class GroupEditor extends LockOwner { throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { Set uuids = Stream.of(recipientIds) - .map(r -> Recipient.resolved(r).requireServiceId().uuid()) + .map(r -> Recipient.resolved(r).requireServiceId().getRawUuid()) .collect(Collectors.toSet()); return commitChangeWithConflictResolution(selfAci, groupOperations.createApproveGroupJoinRequest(uuids)); @@ -442,7 +442,7 @@ final class GroupEditor extends LockOwner { throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { Set uuids = Stream.of(recipientIds) - .map(r -> Recipient.resolved(r).requireServiceId().uuid()) + .map(r -> Recipient.resolved(r).requireServiceId().getRawUuid()) .collect(Collectors.toSet()); return commitChangeWithConflictResolution(selfAci, groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList())); @@ -454,7 +454,7 @@ final class GroupEditor extends LockOwner { throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { Recipient recipient = Recipient.resolved(recipientId); - return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMemberRole(recipient.requireServiceId().uuid(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT)); + return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMemberRole(recipient.requireServiceId().getRawUuid(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT)); } @WorkerThread @@ -463,9 +463,9 @@ void leaveGroup(boolean sendToMembers) { GroupRecord groupRecord = groupDatabase.requireGroup(groupId); DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup(); - Optional selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.uuid()); - Optional aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.uuid()); - Optional pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.uuid()); + Optional selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.getRawUuid()); + Optional aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.getRawUuid()); + Optional pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.getRawUuid()); Optional selfPendingMember = Optional.empty(); ServiceId serviceId = selfAci; @@ -494,7 +494,7 @@ void leaveGroup(boolean sendToMembers) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { return commitChangeWithConflictResolution(selfAci, - groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), + groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.getRawUuid()), ban, ban ? v2GroupProperties.getDecryptedGroup().getBannedMembersList() : Collections.emptyList()), @@ -506,9 +506,9 @@ void leaveGroup(boolean sendToMembers) @NonNull GroupManager.GroupActionResult addMemberAdminsAndLeaveGroup(Collection newAdmins) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { - List newAdminRecipients = Stream.of(newAdmins).map(id -> Recipient.resolved(id).requireServiceId().uuid()).toList(); + List newAdminRecipients = Stream.of(newAdmins).map(id -> Recipient.resolved(id).requireServiceId().getRawUuid()).toList(); - return commitChangeWithConflictResolution(selfAci, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci.uuid(), + return commitChangeWithConflictResolution(selfAci, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci.getRawUuid(), newAdminRecipients)); } @@ -518,7 +518,7 @@ void leaveGroup(boolean sendToMembers) { ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey(); DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup(); - Optional selfInGroup = DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfAci.uuid()); + Optional selfInGroup = DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfAci.getRawUuid()); if (!selfInGroup.isPresent()) { Log.w(TAG, "Self not in group " + groupId); @@ -548,15 +548,15 @@ void leaveGroup(boolean sendToMembers) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup(); - Optional selfInGroup = DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfAci.uuid()); + Optional selfInGroup = DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfAci.getRawUuid()); if (selfInGroup.isPresent()) { Log.w(TAG, "Self already in group"); return null; } - Optional aciInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfAci.uuid()); - Optional pniInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfPni.uuid()); + Optional aciInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfAci.getRawUuid()); + Optional pniInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfPni.getRawUuid()); GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); @@ -650,7 +650,7 @@ public GroupManager.GroupActionResult cycleGroupLinkPassword() throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { boolean refetchedAddMemberCredentials = false; - change.setSourceUuid(UuidUtil.toByteString(authServiceId.uuid())); + change.setSourceUuid(UuidUtil.toByteString(authServiceId.getRawUuid())); for (int attempt = 0; attempt < 5; attempt++) { try { @@ -704,7 +704,7 @@ private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServi GroupChange.Actions changeActions = change.build(); return GroupChangeUtil.resolveConflict(groupUpdateResult.getLatestServer(), - groupOperations.decryptChange(changeActions, authServiceId.uuid()), + groupOperations.decryptChange(changeActions, authServiceId.getRawUuid()), changeActions); } catch (VerificationFailedException | InvalidGroupStateException ex) { throw new GroupChangeFailedException(ex); @@ -1203,7 +1203,7 @@ private boolean testGroupMembership() void cancelJoinRequest() throws GroupChangeFailedException, IOException { - Set uuids = Collections.singleton(selfAci.uuid()); + Set uuids = Collections.singleton(selfAci.getRawUuid()); GroupChange signedGroupChange; try { @@ -1331,8 +1331,9 @@ static class SendGroupUpdateHelper { } private static @NonNull List getPendingMemberRecipientIds(@NonNull List newPendingMembersList) { + // TODO [greyson][ServiceId] Pending members can be ACI's or PNI's return Stream.of(DecryptedGroupUtil.pendingToUuidList(newPendingMembersList)) - .map(uuid -> RecipientId.from(ServiceId.from(uuid))) + .map(uuid -> RecipientId.from(ACI.from(uuid))) .toList(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java index 2a7edab746..7c06d58f3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java @@ -80,12 +80,12 @@ public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull Gro @WorkerThread public static Recipient pendingMemberToRecipient(@NonNull Context context, @NonNull DecryptedPendingMember pendingMember) { - return uuidByteStringToRecipient(context, pendingMember.getUuid()); + return pendingMemberServiceIdToRecipient(context, pendingMember.getUuid()); } @WorkerThread - public static Recipient uuidByteStringToRecipient(@NonNull Context context, @NonNull ByteString uuidByteString) { - ServiceId serviceId = ServiceId.fromByteString(uuidByteString); + public static Recipient pendingMemberServiceIdToRecipient(@NonNull Context context, @NonNull ByteString uuidByteString) { + ServiceId serviceId = ServiceId.parseOrThrow(uuidByteString); if (serviceId.isUnknown()) { return Recipient.UNKNOWN; @@ -96,7 +96,7 @@ public static Recipient uuidByteStringToRecipient(@NonNull Context context, @Non @WorkerThread public static @NonNull RecipientId uuidByteStringToRecipientId(@NonNull ByteString uuidByteString) { - ServiceId serviceId = ServiceId.fromByteString(uuidByteString); + ServiceId serviceId = ServiceId.parseOrThrow(uuidByteString); if (serviceId.isUnknown()) { return RecipientId.UNKNOWN; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java index 075ded4436..4c4de6a521 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java @@ -4,7 +4,6 @@ import org.signal.core.util.logging.Log; import org.signal.libsignal.zkgroup.GenericServerPublicParams; -import org.signal.libsignal.zkgroup.GenericServerSecretParams; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredential; @@ -12,9 +11,6 @@ import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse; import org.signal.libsignal.zkgroup.calllinks.CallLinkSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; @@ -130,13 +126,13 @@ private CallLinkAuthCredentialPresentation getCallLinkAuthCredentialPresentation } CallLinkAuthCredential credential = authCredentialResponse.receive( - Recipient.self().requireServiceId().uuid(), + Recipient.self().requireServiceId().getRawUuid(), Instant.ofEpochSecond(todaySeconds), genericServerPublicParams ); return credential.present( - Recipient.self().requireServiceId().uuid(), + Recipient.self().requireServiceId().getRawUuid(), Instant.ofEpochSecond(todaySeconds), genericServerPublicParams, callLinkSecretParams diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java b/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java index 2e469b2a81..e41287a6f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java @@ -111,7 +111,7 @@ protected static LiveData> mapToRequesti return Stream.of(requestingMembersList) .map(requestingMember -> { - Recipient recipient = Recipient.externalPush(ServiceId.fromByteString(requestingMember.getUuid())); + Recipient recipient = Recipient.externalPush(ServiceId.parseOrThrow(requestingMember.getUuid())); return new GroupMemberEntry.RequestingMember(recipient, selfAdmin); }) .toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/invited/PendingMemberInvitesRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/invited/PendingMemberInvitesRepository.java index cf414f5a2c..af63517fdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/invited/PendingMemberInvitesRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/invited/PendingMemberInvitesRepository.java @@ -77,7 +77,7 @@ public void getInvitees(@NonNull Consumer onInviteesLoaded) { } } } else { - Recipient inviter = GroupProtoUtil.uuidByteStringToRecipient(context, inviterUuid); + Recipient inviter = GroupProtoUtil.pendingMemberServiceIdToRecipient(context, inviterUuid); ArrayList uuidCipherTexts = new ArrayList<>(invitedMembers.size()); for (DecryptedPendingMember pendingMember : invitedMembers) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java index 6f3f807ab7..8344331aa2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java @@ -51,7 +51,7 @@ public GroupCandidateHelper() { } Optional expiringProfileKeyCredential = Optional.ofNullable(recipient.getExpiringProfileKeyCredential()); - GroupCandidate candidate = new GroupCandidate(serviceId.uuid(), expiringProfileKeyCredential); + GroupCandidate candidate = new GroupCandidate(serviceId.getRawUuid(), expiringProfileKeyCredential); if (!candidate.hasValidProfileKeyCredential()) { recipientTable.clearProfileKeyCredential(recipient.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySet.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySet.java index 35ac4542ca..da586e0cd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySet.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySet.java @@ -12,6 +12,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -97,11 +98,11 @@ private void addMemberKey(@Nullable UUID changeSource, } if (memberUuid.equals(changeSource)) { - authoritativeProfileKeys.put(ServiceId.from(memberUuid), profileKey); - profileKeys.remove(ServiceId.from(memberUuid)); + authoritativeProfileKeys.put(ACI.from(memberUuid), profileKey); + profileKeys.remove(ACI.from(memberUuid)); } else { - if (!authoritativeProfileKeys.containsKey(ServiceId.from(memberUuid))) { - profileKeys.put(ServiceId.from(memberUuid), profileKey); + if (!authoritativeProfileKeys.containsKey(ACI.from(memberUuid))) { + profileKeys.put(ACI.from(memberUuid), profileKey); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index f9f3bd4334..aba0807edc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -55,7 +55,7 @@ import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException; import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException; import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException; @@ -171,13 +171,13 @@ private StateProcessorForGroup(@NonNull ServiceIds serviceIds, } private StateProcessorForGroup(@NonNull ServiceIds serviceIds, - @NonNull Context context, - @NonNull GroupTable groupDatabase, - @NonNull GroupsV2Api groupsV2Api, - @NonNull GroupsV2Authorization groupsV2Authorization, - @NonNull GroupMasterKey groupMasterKey, - @NonNull GroupSecretParams groupSecretParams, - @NonNull RecipientTable recipientTable) + @NonNull Context context, + @NonNull GroupTable groupDatabase, + @NonNull GroupsV2Api groupsV2Api, + @NonNull GroupsV2Authorization groupsV2Authorization, + @NonNull GroupMasterKey groupMasterKey, + @NonNull GroupSecretParams groupSecretParams, + @NonNull RecipientTable recipientTable) { this.serviceIds = serviceIds; this.context = context; @@ -425,23 +425,23 @@ private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, Decrypte throw new IOException(e); } - if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci().uuid(), localState.getMembersList())) { + if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci().getRawUuid(), localState.getMembersList())) { info("Local state is at or later than server"); return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null); } - if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceIds.getAci().uuid(), latestServerGroup.getMembersList())) { + if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceIds.getAci().getRawUuid(), latestServerGroup.getMembersList())) { info("Latest revision or not a member, use latest only"); inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null))); } else { - int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci().uuid()); + int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci().getRawUuid()); int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded; boolean includeFirstState = forceIncludeFirst || localState == null || localState.getRevision() < 0 || localState.getRevision() == revisionWeWereAdded || - !GroupProtoUtil.isMember(serviceIds.getAci().uuid(), localState.getMembersList()) || + !GroupProtoUtil.isMember(serviceIds.getAci().getRawUuid(), localState.getMembersList()) || (revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision()); info("Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") + @@ -556,7 +556,7 @@ private void insertGroupLeave() { } Recipient groupRecipient = Recipient.externalGroupExact(groupId); - UUID selfUuid = serviceIds.getAci().uuid(); + UUID selfUuid = serviceIds.getAci().getRawUuid(); DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId) .requireV2GroupProperties() @@ -668,17 +668,17 @@ private void warn(String message, Throwable e) { @VisibleForTesting static class ProfileAndMessageHelper { - private final Context context; - private final ServiceId serviceId; + private final Context context; + private final ACI aci; private final GroupId.V2 groupId; private final RecipientTable recipientTable; @VisibleForTesting GroupMasterKey masterKey; - ProfileAndMessageHelper(@NonNull Context context, @NonNull ServiceId serviceId, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientTable recipientTable) { + ProfileAndMessageHelper(@NonNull Context context, @NonNull ACI aci, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientTable recipientTable) { this.context = context; - this.serviceId = serviceId; + this.aci = aci; this.masterKey = masterKey; this.groupId = groupId; this.recipientTable = recipientTable; @@ -686,15 +686,15 @@ static class ProfileAndMessageHelper { void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull DecryptedGroup newLocalState) { if (inputGroupState.getLocalState() != null) { - boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), serviceId.uuid()).isPresent(); + boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), aci.getRawUuid()).isPresent(); if (wasAMemberAlready) { return; } } - Optional selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), serviceId.uuid()); - Optional selfAsPendingOptional = DecryptedGroupUtil.findPendingByUuid(newLocalState.getPendingMembersList(), serviceId.uuid()); + Optional selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), aci.getRawUuid()); + Optional selfAsPendingOptional = DecryptedGroupUtil.findPendingByUuid(newLocalState.getPendingMembersList(), aci.getRawUuid()); if (selfAsMemberOptional.isPresent()) { DecryptedMember selfAsMember = selfAsMemberOptional.get(); @@ -705,7 +705,7 @@ void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull .filter(c -> c != null && c.getRevision() == revisionJoinedAt) .findFirst() .map(c -> Optional.ofNullable(UuidUtil.fromByteStringOrNull(c.getEditor())) - .map(uuid -> Recipient.externalPush(ServiceId.from(uuid)))) + .map(uuid -> Recipient.externalPush(ACI.from(uuid)))) .orElse(Optional.empty()); if (addedByOptional.isPresent()) { @@ -713,7 +713,7 @@ void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId())); - if (addedBy.isBlocked() && (inputGroupState.getLocalState() == null || !DecryptedGroupUtil.isRequesting(inputGroupState.getLocalState(), serviceId.uuid()))) { + if (addedBy.isBlocked() && (inputGroupState.getLocalState() == null || !DecryptedGroupUtil.isRequesting(inputGroupState.getLocalState(), aci.getRawUuid()))) { Log.i(TAG, "Added by a blocked user. Leaving group."); ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId)); //noinspection UnnecessaryReturnStatement @@ -730,7 +730,7 @@ void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull } } else if (selfAsPendingOptional.isPresent()) { Optional addedBy = selfAsPendingOptional.flatMap(adder -> Optional.ofNullable(UuidUtil.fromByteStringOrNull(adder.getAddedByUuid())) - .map(uuid -> Recipient.externalPush(ServiceId.from(uuid)))); + .map(uuid -> Recipient.externalPush(ACI.from(uuid)))); if (addedBy.isPresent() && addedBy.get().isBlocked()) { Log.i(TAG, String.format("Added to group %s by a blocked user %s. Leaving group.", groupId, addedBy.get().getId())); @@ -795,9 +795,9 @@ void persistLearnedProfileKeys(@NonNull ProfileKeySet profileKeys) { } void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) { - Optional editor = getEditor(decryptedGroupV2Context).map(ServiceId::from); + Optional editor = getEditor(decryptedGroupV2Context).map(ACI::from); - boolean outgoing = !editor.isPresent() || serviceId.equals(editor.get()); + boolean outgoing = !editor.isPresent() || aci.equals(editor.get()); if (outgoing) { try { @@ -835,7 +835,7 @@ private Optional getEditor(@NonNull DecryptedGroupV2Context decryptedGroup if (changeEditor.isPresent()) { return changeEditor; } else { - Optional pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), serviceId.uuid()); + Optional pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), aci.getRawUuid()); if (pendingByUuid.isPresent()) { return Optional.ofNullable(UuidUtil.fromByteStringOrNull(pendingByUuid.get().getAddedByUuid())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java index dcaaed5eb2..84b8ac0721 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java @@ -85,7 +85,7 @@ private AutomaticSessionResetJob(@NonNull Parameters parameters, @Override protected void onRun() throws Exception { - ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(recipientId, deviceId); + ApplicationDependencies.getProtocolStore().aci().sessions().archiveSessions(recipientId, deviceId); SignalDatabase.senderKeyShared().deleteAllFor(recipientId); insertLocalMessage(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java index 37eac65d31..04b597db36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java @@ -119,7 +119,7 @@ public static void enqueueForGroupsIfNecessary() { continue; } - ByteString selfUuidBytes = UuidUtil.toByteString(Recipient.self().requireServiceId().uuid()); + ByteString selfUuidBytes = UuidUtil.toByteString(Recipient.self().requireAci().getRawUuid()); DecryptedMember selfMember = group.get().requireV2GroupProperties().getDecryptedGroup().getMembersList() .stream() .filter(m -> m.getUuid().equals(selfUuidBytes)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceStorySendSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceStorySendSyncJob.kt index a456ff9435..f97c9dea16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceStorySendSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceStorySendSyncJob.kt @@ -74,7 +74,7 @@ class MultiDeviceStorySendSyncJob private constructor(parameters: Parameters, pr private fun buildSentTranscript(recipientsSet: Set): SentTranscriptMessage { return SentTranscriptMessage( - Optional.of(SignalServiceAddress(Recipient.self().requireServiceId())), + Optional.of(SignalServiceAddress(Recipient.self().requireAci())), sentTimestamp, Optional.empty(), 0, diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java index 40e27e06c2..6fe3e6e401 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java @@ -33,7 +33,7 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; @@ -77,8 +77,8 @@ public final class PushGroupSilentUpdateSendJob extends BaseJob { Set recipients = Stream.concat(Stream.of(memberUuids), Stream.of(pendingUuids)) .filter(uuid -> !UuidUtil.UNKNOWN_UUID.equals(uuid)) - .filter(uuid -> !SignalStore.account().requireAci().uuid().equals(uuid)) - .map(uuid -> Recipient.externalPush(ServiceId.from(uuid))) + .filter(uuid -> !SignalStore.account().requireAci().getRawUuid().equals(uuid)) + .map(uuid -> Recipient.externalPush(ACI.from(uuid))) .filter(recipient -> recipient.getRegistered() != RecipientTable.RegisteredState.NOT_REGISTERED) .map(Recipient::getId) .collect(Collectors.toSet()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index 042abd8a05..b1643eecbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -67,6 +67,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.shared.SharedContact; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; @@ -325,7 +326,7 @@ protected static void notifyMediaMessageDeliveryFailed(Context context, long mes protected Optional getQuoteFor(OutgoingMessage message) throws IOException { if (message.getOutgoingQuote() == null) return Optional.empty(); if (message.isMessageEdit()) { - return Optional.of(new SignalServiceDataMessage.Quote(0, ServiceId.UNKNOWN, "", null, null, SignalServiceDataMessage.Quote.Type.NORMAL, null)); + return Optional.of(new SignalServiceDataMessage.Quote(0, ACI.UNKNOWN, "", null, null, SignalServiceDataMessage.Quote.Type.NORMAL, null)); } long quoteId = message.getOutgoingQuote().getId(); @@ -383,7 +384,7 @@ protected Optional getQuoteFor(OutgoingMessage m if (quoteAuthorRecipient.isMaybeRegistered()) { return Optional.of(new SignalServiceDataMessage.Quote(quoteId, RecipientUtil.getOrFetchServiceId(context, quoteAuthorRecipient), quoteBody, quoteAttachments, quoteMentions, quoteType.getDataMessageType(), bodyRanges)); } else if (quoteAuthorRecipient.hasServiceId()) { - return Optional.of(new SignalServiceDataMessage.Quote(quoteId, quoteAuthorRecipient.requireServiceId(), quoteBody, quoteAttachments, quoteMentions, quoteType.getDataMessageType(), bodyRanges)); + return Optional.of(new SignalServiceDataMessage.Quote(quoteId, quoteAuthorRecipient.requireAci(), quoteBody, quoteAttachments, quoteMentions, quoteType.getDataMessageType(), bodyRanges)); } else { return Optional.empty(); } @@ -451,7 +452,7 @@ List getPreviewsFor(OutgoingMessage mediaMessage) { List getMentionsFor(@NonNull List mentions) { return Stream.of(mentions) - .map(m -> new SignalServiceDataMessage.Mention(Recipient.resolved(m.getRecipientId()).requireServiceId(), m.getStart(), m.getLength())) + .map(m -> new SignalServiceDataMessage.Mention(Recipient.resolved(m.getRecipientId()).requireAci(), m.getStart(), m.getLength())) .toList(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt index 5b74927b7a..4125a315b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt @@ -22,8 +22,8 @@ import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.Util -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceIds import org.whispersystems.signalservice.api.push.SignalServiceAddress import java.security.SecureRandom @@ -85,6 +85,12 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal if (!store.containsKey(KEY_ACI_IDENTITY_PUBLIC_KEY)) { migrateFromSharedPrefsV2(ApplicationDependencies.getApplication()) } + + store.getString(KEY_PNI, null)?.let { pni -> + if (!pni.startsWith("PNI:")) { + store.beginWrite().putString(KEY_PNI, "PNI:$pni").commit() + } + } } public override fun onFirstEverAppLaunch() = Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java index 8a2bd37380..ad024c7f45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java @@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.VersionTracker; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Arrays; import java.util.LinkedList; diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/CallMessageProcessor.kt index 169c79ab16..b0b16beace 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/CallMessageProcessor.kt @@ -42,7 +42,7 @@ object CallMessageProcessor { handleCallHangupMessage(envelope, metadata, hangup, senderRecipient.id, callMessage.hasLegacyHangup()) } callMessage.hasBusy() -> handleCallBusyMessage(envelope, metadata, callMessage.busy, senderRecipient.id) - callMessage.hasOpaque() -> handleCallOpaqueMessage(envelope, metadata, callMessage.opaque, senderRecipient.requireServiceId(), serverDeliveredTimestamp) + callMessage.hasOpaque() -> handleCallOpaqueMessage(envelope, metadata, callMessage.opaque, senderRecipient.requireAci(), serverDeliveredTimestamp) } } @@ -146,7 +146,7 @@ object CallMessageProcessor { ApplicationDependencies.getSignalCallManager() .receivedOpaqueMessage( OpaqueMessageMetadata( - senderServiceId.uuid(), + senderServiceId.rawUuid, opaque.data.toByteArray(), metadata.sourceDeviceId, messageAgeSeconds diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt index 4ea1030467..53c9bae541 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt @@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.util.isStory import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata import org.whispersystems.signalservice.api.payments.Money import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content @@ -992,10 +993,10 @@ object DataMessageProcessor { return mentionBodyRanges .filter { it.hasMentionAci() } .mapNotNull { - val serviceId = ServiceId.parseOrNull(it.mentionAci) + val aci = ACI.parseOrNull(it.mentionAci) - if (serviceId != null && !serviceId.isUnknown) { - val id = Recipient.externalPush(serviceId).id + if (aci != null && !aci.isUnknown) { + val id = Recipient.externalPush(aci).id Mention(id, it.start, it.length) } else { null diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index cba4db49ee..e30e324507 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -537,7 +537,7 @@ private boolean handleGv2PreProcessing(@NonNull GroupId.V2 groupId, @NonNull Sig Optional groupRecord = groupDatabase.getGroup(groupId); if (groupRecord.isPresent() && !groupRecord.get().getMembers().contains(senderRecipient.getId())) { - log(String.valueOf(content.getTimestamp()), "Ignoring GV2 message from member not in group " + groupId + ". Sender: " + senderRecipient.getId() + " | " + senderRecipient.requireServiceId()); + log(String.valueOf(content.getTimestamp()), "Ignoring GV2 message from member not in group " + groupId + ". Sender: " + senderRecipient.getId() + " | " + senderRecipient.requireAci()); return true; } @@ -739,7 +739,7 @@ private void handleCallOpaqueMessage(@NonNull SignalServiceContent content, } ApplicationDependencies.getSignalCallManager() - .receivedOpaqueMessage(new WebRtcData.OpaqueMessageMetadata(senderRecipient.requireServiceId().uuid(), + .receivedOpaqueMessage(new WebRtcData.OpaqueMessageMetadata(senderRecipient.requireAci().getRawUuid(), message.getOpaque(), content.getSenderDevice(), messageAgeSeconds)); @@ -843,7 +843,7 @@ private void handleUnknownGroupMessage(@NonNull SignalServiceContent content, log(content.getTimestamp(), "Unknown group message."); warn(content.getTimestamp(), "Received a GV2 message for a group we have no knowledge of -- attempting to fix this state."); - ServiceId authServiceId = ServiceId.parseOrNull(content.getDestinationUuid()); + ServiceId authServiceId = ServiceId.parseOrNull(content.getDestinationServiceId()); if (authServiceId == null) { warn(content.getTimestamp(), "Group message missing destination uuid, defaulting to ACI"); authServiceId = SignalStore.account().requireAci(); @@ -1290,7 +1290,7 @@ private void handleSynchronizeCallEvent(@NonNull SyncMessage.CallEvent callEvent return; } - ServiceId serviceId = ServiceId.fromByteString(callEvent.getConversationId()); + ServiceId serviceId = ServiceId.parseOrThrow(callEvent.getConversationId()); RecipientId recipientId = RecipientId.from(serviceId); log(envelopeTimestamp, "Synchronize call event call: " + callId); @@ -3061,7 +3061,7 @@ private void handleIndividualRetryReceipt(@NonNull Recipient requester, @Nullabl ratchetKeyMatches(requester, content.getSenderDevice(), decryptionErrorMessage.getRatchetKey().get())) { warn(content.getTimestamp(), "[RetryReceipt-I] Ratchet key matches. Archiving the session."); - ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.getId(), content.getSenderDevice()); + ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), content.getSenderDevice()); archivedSession = true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt index 734fe638cf..deadda5677 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt @@ -569,7 +569,7 @@ open class MessageContentProcessorV2(private val context: Context) { ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get()) ) { warn(envelope.timestamp, "[RetryReceipt-I] Ratchet key matches. Archiving the session.") - ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.id, metadata.sourceDeviceId) + ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId) archivedSession = true } @@ -604,7 +604,7 @@ open class MessageContentProcessorV2(private val context: Context) { } private fun ratchetKeyMatches(recipient: Recipient, deviceId: Int, ratchetKey: ECPublicKey): Boolean { - val address = recipient.resolve().requireServiceId().toProtocolAddress(deviceId) + val address = recipient.resolve().requireAci().toProtocolAddress(deviceId) val session = ApplicationDependencies.getProtocolStore().aci().loadSession(address) return session.currentRatchetKeyMatches(ratchetKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index 05660a2406..f694922f9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -55,8 +55,9 @@ import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder import org.whispersystems.signalservice.api.crypto.SignalServiceCipher import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator -import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope @@ -158,13 +159,17 @@ object MessageDecryptor { } if (FeatureFlags.phoneNumberPrivacy() && cipherResult.content.hasPniSignatureMessage()) { - handlePniSignatureMessage( - envelope, - cipherResult.metadata.sourceServiceId, - cipherResult.metadata.sourceE164, - cipherResult.metadata.sourceDeviceId, - cipherResult.content.pniSignatureMessage - ) + if (cipherResult.metadata.sourceServiceId is ACI) { + handlePniSignatureMessage( + envelope, + cipherResult.metadata.sourceServiceId as ACI, + cipherResult.metadata.sourceE164, + cipherResult.metadata.sourceDeviceId, + cipherResult.content.pniSignatureMessage + ) + } else { + Log.w(TAG, "${logPrefix(envelope)} Ignoring PNI signature because the sourceServiceId isn't an ACI!") + } } else if (cipherResult.content.hasPniSignatureMessage()) { Log.w(TAG, "${logPrefix(envelope)} Ignoring PNI signature because the feature flag is disabled!") } @@ -295,37 +300,37 @@ object MessageDecryptor { SignalGroupSessionBuilder(ReentrantSessionLock.INSTANCE, GroupSessionBuilder(senderKeyStore)).process(sender, message) } - private fun handlePniSignatureMessage(envelope: Envelope, serviceId: ServiceId, e164: String?, deviceId: Int, pniSignatureMessage: PniSignatureMessage) { - Log.i(TAG, "${logPrefix(envelope, serviceId)} Processing PniSignatureMessage") + private fun handlePniSignatureMessage(envelope: Envelope, aci: ACI, e164: String?, deviceId: Int, pniSignatureMessage: PniSignatureMessage) { + Log.i(TAG, "${logPrefix(envelope, aci)} Processing PniSignatureMessage") val pni: PNI = PNI.parseOrThrow(pniSignatureMessage.pni.toByteArray()) - if (SignalDatabase.recipients.isAssociated(serviceId, pni)) { - Log.i(TAG, "${logPrefix(envelope, serviceId)}[handlePniSignatureMessage] ACI ($serviceId) and PNI ($pni) are already associated.") + if (SignalDatabase.recipients.isAssociated(aci, pni)) { + Log.i(TAG, "${logPrefix(envelope, aci)}[handlePniSignatureMessage] ACI ($aci) and PNI ($pni) are already associated.") return } val identityStore = ApplicationDependencies.getProtocolStore().aci().identities() - val aciAddress = SignalProtocolAddress(serviceId.toString(), deviceId) + val aciAddress = SignalProtocolAddress(aci.toString(), deviceId) val pniAddress = SignalProtocolAddress(pni.toString(), deviceId) val aciIdentity = identityStore.getIdentity(aciAddress) val pniIdentity = identityStore.getIdentity(pniAddress) if (aciIdentity == null) { - Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for ACI address $aciAddress") + Log.w(TAG, "${logPrefix(envelope, aci)}[validatePniSignature] No identity found for ACI address $aciAddress") return } if (pniIdentity == null) { - Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for PNI address $pniAddress") + Log.w(TAG, "${logPrefix(envelope, aci)}[validatePniSignature] No identity found for PNI address $pniAddress") return } if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.signature.toByteArray())) { - Log.i(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] PNI signature is valid. Associating ACI ($serviceId) with PNI ($pni)") - SignalDatabase.recipients.getAndPossiblyMergePnpVerified(serviceId, pni, e164) + Log.i(TAG, "${logPrefix(envelope, aci)}[validatePniSignature] PNI signature is valid. Associating ACI ($aci) with PNI ($pni)") + SignalDatabase.recipients.getAndPossiblyMergePnpVerified(aci, pni, e164) } else { - Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] Invalid PNI signature! Cannot associate ACI ($serviceId) with PNI ($pni)") + Log.w(TAG, "${logPrefix(envelope, aci)}[validatePniSignature] Invalid PNI signature! Cannot associate ACI ($aci) with PNI ($pni)") } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt index 631a10da79..8ae07c9458 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt @@ -98,6 +98,7 @@ import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.storage.StorageKey import org.whispersystems.signalservice.internal.push.SignalServiceProtos @@ -1227,8 +1228,8 @@ object SyncMessageProcessor { return } - val serviceId = ServiceId.fromByteString(callEvent.conversationId) - val recipientId = RecipientId.from(serviceId) + val aci = ACI.parseOrThrow(callEvent.conversationId) + val recipientId = RecipientId.from(aci) log(envelopeTimestamp, "Synchronize call event call: $callId") diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java index c0840b86a0..2b790eca84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.account.PreKeyUpload; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceIdType; import java.io.IOException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniMigrationJob.java index aa9a6f9a24..6035f6cdc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniMigrationJob.java @@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import java.io.IOException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java index acf49234d9..81368b2cc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/UuidMigrationJob.java @@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.io.IOException; import java.util.Objects; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java b/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java index 2a7a675b3b..851d716439 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java @@ -191,7 +191,7 @@ private GroupV2Properties(DecryptedGroupV2Context decryptedGroupV2Context) { List members = new ArrayList<>(decryptedGroupV2Context.getGroupState().getMembersCount()); for (DecryptedMember member : decryptedGroupV2Context.getGroupState().getMembersList()) { - RecipientId recipient = RecipientId.from(ServiceId.fromByteString(member.getUuid())); + RecipientId recipient = RecipientId.from(ServiceId.parseOrThrow(member.getUuid())); if (!Recipient.self().getId().equals(recipient)) { members.add(recipient); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java index 89e90cf491..1ba47d6223 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java @@ -13,8 +13,8 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.util.FeatureFlags; import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; public class AccountManagerFactory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 0b7bd86316..59fe72a5cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.util.LRUCache; import org.signal.core.util.Stopwatch; import org.thoughtcrime.securesms.util.concurrent.FilteredExecutor; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.ArrayList; import java.util.Collection; @@ -160,7 +160,7 @@ public void addToCache(@NonNull Collection newRecipients) { } if (localAci != null) { - selfId = recipientTable.getByServiceId(localAci).orElse(null); + selfId = recipientTable.getByAci(localAci).orElse(null); } if (selfId == null && localE164 != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 3a96dce69e..f35bf4cb23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -49,7 +49,8 @@ import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.OptionalUtil; @@ -86,7 +87,7 @@ public class Recipient { private final RecipientId id; private final boolean resolving; - private final ServiceId serviceId; + private final ACI aci; private final PNI pni; private final String username; private final String e164; @@ -219,8 +220,8 @@ public class Recipient { * Create a recipient with a full (ACI, PNI, E164) tuple. It is assumed that the association between the PNI and serviceId is trusted. * That means it must be from either storage service or a PNI verification message. */ - public static @NonNull Recipient trustedPush(@NonNull ServiceId serviceId, @Nullable PNI pni, @Nullable String e164) { - if (ServiceId.UNKNOWN.equals(serviceId)) { + public static @NonNull Recipient trustedPush(@NonNull ACI aci, @Nullable PNI pni, @Nullable String e164) { + if (ACI.UNKNOWN.equals(aci) || PNI.UNKNOWN.equals(pni)) { throw new AssertionError("Unknown serviceId!"); } @@ -229,9 +230,9 @@ public class Recipient { RecipientId recipientId; if (FeatureFlags.phoneNumberPrivacy()) { - recipientId = db.getAndPossiblyMergePnpVerified(serviceId, pni, e164); + recipientId = db.getAndPossiblyMergePnpVerified(aci, pni, e164); } else { - recipientId = db.getAndPossiblyMerge(serviceId, e164); + recipientId = db.getAndPossiblyMerge(aci, e164); } Recipient resolved = resolved(recipientId); @@ -242,7 +243,7 @@ public class Recipient { if (!resolved.isRegistered()) { Log.w(TAG, "External push was locally marked unregistered. Marking as registered."); - db.markRegistered(recipientId, serviceId); + db.markRegistered(recipientId, aci); } return resolved; @@ -259,7 +260,7 @@ public class Recipient { */ @WorkerThread static @NonNull Recipient externalPush(@Nullable ServiceId serviceId, @Nullable String e164) { - if (ServiceId.UNKNOWN.equals(serviceId)) { + if (ACI.UNKNOWN.equals(serviceId) || PNI.UNKNOWN.equals(serviceId)) { throw new AssertionError(); } @@ -375,7 +376,7 @@ public static boolean isSelfSet() { Recipient(@NonNull RecipientId id) { this.id = id; this.resolving = true; - this.serviceId = null; + this.aci = null; this.pni = null; this.username = null; this.e164 = null; @@ -433,7 +434,7 @@ public static boolean isSelfSet() { public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) { this.id = id; this.resolving = !resolved; - this.serviceId = details.serviceId; + this.aci = details.aci; this.pni = details.pni; this.username = details.username; this.e164 = details.e164; @@ -661,7 +662,11 @@ public boolean hasNonUsernameDisplayName(@NonNull Context context) { } public @NonNull Optional getServiceId() { - return Optional.ofNullable(serviceId); + return OptionalUtil.or(Optional.ofNullable(aci), Optional.ofNullable(pni)); + } + + public @NonNull Optional getAci() { + return Optional.ofNullable(aci); } public @NonNull Optional getPni() { @@ -750,6 +755,14 @@ public boolean hasServiceId() { return getServiceId().isPresent(); } + public boolean hasAci() { + return getAci().isPresent(); + } + + public boolean hasPni() { + return getPni().isPresent(); + } + public boolean isServiceIdOnly() { return hasServiceId() && !hasSmsAddress(); } @@ -786,7 +799,22 @@ public boolean hasViewedStory() { * The {@link ServiceId} of the user if available, otherwise throw. */ public @NonNull ServiceId requireServiceId() { - ServiceId resolved = resolving ? resolve().serviceId : serviceId; + Recipient resolved = resolving ? resolve() : this; + + if (resolved.aci != null) { + return resolved.aci; + } else if (resolved.pni != null) { + return resolved.pni; + } else { + throw new MissingAddressError(id); + } + } + + /** + * The {@link ACI} of the user if available, otherwise throw. + */ + public @NonNull ACI requireAci() { + ACI resolved = resolving ? resolve().aci : aci; if (resolved == null) { throw new MissingAddressError(id); @@ -1326,7 +1354,7 @@ public boolean hasSameContent(@NonNull Recipient other) { profileSharing == other.profileSharing && isHidden == other.isHidden && forceSmsSelection == other.forceSmsSelection && - Objects.equals(serviceId, other.serviceId) && + Objects.equals(aci, other.aci) && Objects.equals(username, other.username) && Objects.equals(e164, other.e164) && Objects.equals(email, other.email) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 76b3c3860d..26b60b426c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -26,8 +26,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; -import org.whispersystems.signalservice.api.push.PNI; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import java.util.Collections; import java.util.LinkedList; @@ -36,7 +36,7 @@ public class RecipientDetails { - final ServiceId serviceId; + final ACI aci; final PNI pni; final String username; final String e164; @@ -108,7 +108,7 @@ public RecipientDetails(@Nullable String groupName, this.systemContactPhoto = Util.uri(record.getSystemContactPhotoUri()); this.customLabel = record.getSystemPhoneLabel(); this.contactUri = Util.uri(record.getSystemContactUri()); - this.serviceId = record.getServiceId(); + this.aci = record.getAci(); this.pni = record.getPni(); this.username = record.getUsername(); this.e164 = record.getE164(); @@ -165,7 +165,7 @@ private RecipientDetails() { this.systemContactPhoto = null; this.customLabel = null; this.contactUri = null; - this.serviceId = null; + this.aci = null; this.pni = null; this.username = null; this.e164 = null; @@ -220,7 +220,7 @@ private RecipientDetails() { public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientRecord settings) { boolean systemContact = !settings.getSystemProfileName().isEmpty(); boolean isSelf = (settings.getE164() != null && settings.getE164().equals(SignalStore.account().getE164())) || - (settings.getServiceId() != null && settings.getServiceId().equals(SignalStore.account().getAci())); + (settings.getAci() != null && settings.getAci().equals(SignalStore.account().getAci())); boolean isReleaseChannel = settings.getId().equals(SignalStore.releaseChannelValues().getReleaseChannelRecipientId()); RegisteredState registeredState = settings.getRegistered(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index 024c090672..218242cf70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -38,8 +38,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.account.PreKeyCollection; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.Preconditions; @@ -129,7 +129,7 @@ private void registerAccountInternal(@NonNull RegistrationData registrationData, Preconditions.checkNotNull(response.getPniPreKeyCollection(), "Missing PNI prekey collection!"); ACI aci = ACI.parseOrThrow(response.getVerifyAccountResponse().getUuid()); - PNI pni = PNI.parseOrThrow(response.getVerifyAccountResponse().getPni()); + PNI pni = PNI.parseUnPrefixedOrThrow(response.getVerifyAccountResponse().getPni()); boolean hasPin = response.getVerifyAccountResponse().isStorageCapable(); SignalStore.account().setAci(aci); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java index 6656975fef..f8a2732a55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java @@ -23,6 +23,7 @@ import org.webrtc.PeerConnection; import org.webrtc.VideoTrack; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.ArrayList; @@ -91,7 +92,7 @@ public GroupActionProcessor(@NonNull MultiPeerActionProcessorFactory actionProce seen.add(Recipient.self()); for (GroupCall.RemoteDeviceState device : remoteDeviceStates) { - Recipient recipient = Recipient.externalPush(ServiceId.from(device.getUserId())); + Recipient recipient = Recipient.externalPush(ACI.from(device.getUserId())); CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId()); CallParticipant callParticipant = participants.get(callParticipantId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupCallRingCheckInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupCallRingCheckInfo.kt index 406e241b55..4be4d74c4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupCallRingCheckInfo.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupCallRingCheckInfo.kt @@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.service.webrtc import org.signal.ringrtc.CallManager import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.recipients.RecipientId -import java.util.UUID +import org.whispersystems.signalservice.api.push.ServiceId.ACI data class GroupCallRingCheckInfo( val recipientId: RecipientId, val groupId: GroupId.V2, val ringId: Long, - val ringerUuid: UUID, + val ringerAci: ACI, val ringUpdate: CallManager.RingUpdate ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java index 96820a6a18..70ff27af89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java @@ -158,8 +158,8 @@ protected GroupConnectedActionProcessor(@NonNull MultiPeerActionProcessorFactory webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, remoteUserRangTheCall, true); List members = new ArrayList<>(peekInfo.getJoinedMembers()); - if (!members.contains(SignalStore.account().requireAci().uuid())) { - members.add(SignalStore.account().requireAci().uuid()); + if (!members.contains(SignalStore.account().requireAci().getRawUuid())) { + members.add(SignalStore.account().requireAci().getRawUuid()); } webRtcInteractor.updateGroupCallUpdateMessage(currentState.getCallInfoState().getCallRecipient().getId(), eraId, members, WebRtcUtil.isCallFull(peekInfo)); @@ -184,7 +184,7 @@ protected GroupConnectedActionProcessor(@NonNull MultiPeerActionProcessorFactory String eraId = WebRtcUtil.getGroupCallEraId(groupCall); webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, false, false); - List members = Stream.of(currentState.getCallInfoState().getRemoteCallParticipants()).map(p -> p.getRecipient().requireServiceId().uuid()).toList(); + List members = Stream.of(currentState.getCallInfoState().getRemoteCallParticipants()).map(p -> p.getRecipient().requireServiceId().getRawUuid()).toList(); webRtcInteractor.updateGroupCallUpdateMessage(currentState.getCallInfoState().getCallRecipient().getId(), eraId, members, false); currentState = currentState.builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java index 167cf73b90..d68cc77ed2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; import org.thoughtcrime.securesms.util.NetworkUtil; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.List; @@ -120,7 +121,7 @@ protected GroupPreJoinActionProcessor(@NonNull MultiPeerActionProcessorFactory a } List callParticipants = Stream.of(peekInfo.getJoinedMembers()) - .map(uuid -> Recipient.externalPush(ServiceId.from(uuid))) + .map(uuid -> Recipient.externalPush(ACI.from(uuid))) .toList(); WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IdleActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IdleActionProcessor.java index 80e872c1da..a46d3da071 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IdleActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IdleActionProcessor.java @@ -17,8 +17,7 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.util.FeatureFlags; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; - -import java.util.UUID; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; /** * Action handler for when the system is at rest. Mainly responsible @@ -97,7 +96,7 @@ public IdleActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) { @NonNull RemotePeer remotePeerGroup, @NonNull GroupId.V2 groupId, long ringId, - @NonNull UUID sender, + @NonNull ACI sender, @NonNull CallManager.RingUpdate ringUpdate) { Log.i(TAG, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + ringId + " update: " + ringUpdate); @@ -154,11 +153,11 @@ public IdleActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) { if (peekInfo.getDeviceCount() == 0) { Log.i(TAG, "No one in the group call, mark as expired and do not ring"); - SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerUuid(), System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST); + SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerAci(), System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST); return currentState; - } else if (peekInfo.getJoinedMembers().contains(Recipient.self().requireServiceId().uuid())) { + } else if (peekInfo.getJoinedMembers().contains(Recipient.self().requireServiceId().getRawUuid())) { Log.i(TAG, "We are already in the call, mark accepted on another device and do not ring"); - SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerUuid(), System.currentTimeMillis(), CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE); + SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerAci(), System.currentTimeMillis(), CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE); return currentState; } @@ -166,6 +165,6 @@ public IdleActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) { .actionProcessor(new IncomingGroupCallActionProcessor(webRtcInteractor)) .build(); - return currentState.getActionProcessor().handleGroupCallRingUpdate(currentState, new RemotePeer(info.getRecipientId()), info.getGroupId(), info.getRingId(), info.getRingerUuid(), info.getRingUpdate()); + return currentState.getActionProcessor().handleGroupCallRingUpdate(currentState, new RemotePeer(info.getRecipientId()), info.getGroupId(), info.getRingId(), info.getRingerAci(), info.getRingUpdate()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java index 3f49eb32e2..46eced2865 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.util.NetworkUtil; import org.thoughtcrime.securesms.webrtc.locks.LockManager; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.Optional; @@ -49,7 +50,7 @@ public IncomingGroupCallActionProcessor(WebRtcInteractor webRtcInteractor) { @NonNull RemotePeer remotePeerGroup, @NonNull GroupId.V2 groupId, long ringId, - @NonNull UUID sender, + @NonNull ACI sender, @NonNull CallManager.RingUpdate ringUpdate) { Log.i(TAG, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + Long.toHexString(ringId) + " update: " + ringUpdate); @@ -108,7 +109,7 @@ public IncomingGroupCallActionProcessor(WebRtcInteractor webRtcInteractor) { Log.i(TAG, "Requesting new ring: " + Long.toHexString(ringId)); - Recipient ringerRecipient = Recipient.externalPush(ServiceId.from(sender)); + Recipient ringerRecipient = Recipient.externalPush(sender); SignalDatabase.calls().insertOrUpdateGroupCallFromRingState( ringId, remotePeerGroup.getId(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java index 54ca921f8d..c48ebfb3ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -80,7 +80,7 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage; @@ -191,7 +191,7 @@ private void process(@NonNull ProcessAction action) { serviceExecutor.execute(() -> { if (needsToSetSelfUuid) { try { - callManager.setSelfUuid(SignalStore.account().requireAci().uuid()); + callManager.setSelfUuid(SignalStore.account().requireAci().getRawUuid()); needsToSetSelfUuid = false; } catch (CallException e) { Log.w(TAG, "Unable to set self UUID on CallManager", e); @@ -724,14 +724,14 @@ public void onSendBusy(@NonNull CallId callId, @Nullable Remote remote, @NonNull } @Override - public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] bytes, @NonNull CallManager.CallMessageUrgency urgency) { + public void onSendCallMessage(@NonNull UUID aciUuid, @NonNull byte[] bytes, @NonNull CallManager.CallMessageUrgency urgency) { Log.i(TAG, "onSendCallMessage():"); OpaqueMessage opaqueMessage = new OpaqueMessage(bytes, getUrgencyFromCallUrgency(urgency)); SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOpaque(opaqueMessage, true, null); networkExecutor.execute(() -> { - Recipient recipient = Recipient.resolved(RecipientId.from(ServiceId.from(uuid))); + Recipient recipient = Recipient.resolved(RecipientId.from(ACI.from(aciUuid))); if (recipient.isBlocked()) { return; } @@ -826,16 +826,17 @@ public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull Call @Override public void onGroupCallRingUpdate(@NonNull byte[] groupIdBytes, long ringId, @NonNull UUID sender, @NonNull CallManager.RingUpdate ringUpdate) { try { + ACI senderAci = ACI.from(sender); GroupId.V2 groupId = GroupId.v2(new GroupIdentifier(groupIdBytes)); GroupRecord group = SignalDatabase.groups().getGroup(groupId).orElse(null); - Recipient senderRecipient = Recipient.externalPush(ServiceId.from(sender)); + Recipient senderRecipient = Recipient.externalPush(senderAci); if (group != null && group.isActive() && !Recipient.resolved(group.getRecipientId()).isBlocked() && (!group.isAnnouncementGroup() || group.isAdmin(senderRecipient))) { - process((s, p) -> p.handleGroupCallRingUpdate(s, new RemotePeer(group.getRecipientId()), groupId, ringId, sender, ringUpdate)); + process((s, p) -> p.handleGroupCallRingUpdate(s, new RemotePeer(group.getRecipientId()), groupId, ringId, senderAci, ringUpdate)); } else { Log.w(TAG, "Unable to ring unknown/inactive/blocked group."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index da6f55c419..6ab8e96646 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -54,6 +54,7 @@ import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Collection; import java.util.List; @@ -789,7 +790,7 @@ public WebRtcActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNul @NonNull RemotePeer remotePeerGroup, @NonNull GroupId.V2 groupId, long ringId, - @NonNull UUID sender, + @NonNull ACI sender, @NonNull RingUpdate ringUpdate) { Log.i(tag, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + ringId + " update: " + ringUpdate); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt index 1b4869a6e5..d1fc094b44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt @@ -45,7 +45,7 @@ class SignalCallLinkManager( linkRootKey: ByteArray, roomId: ByteArray ): CreateCallLinkCredentialPresentation { - val userUuid = Recipient.self().requireServiceId().uuid() + val userUuid = Recipient.self().requireAci().rawUuid val requestContext = CreateCallLinkCredentialRequestContext.forRoom(roomId) val request = requestContext.request diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java index 18a37b3f1b..b1063ae4cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java @@ -12,8 +12,8 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.util.OptionalUtil; @@ -26,7 +26,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Pattern; @@ -81,9 +80,9 @@ public void process(@NonNull Collection remoteRecords, @Non continue; } - if (remoteRecord.getUnregisteredTimestamp() > 0 && remoteRecord.getServiceId() != null && !remoteRecord.getPni().isPresent() && !remoteRecord.getNumber().isPresent()) { + if (remoteRecord.getUnregisteredTimestamp() > 0 && remoteRecord.getAci() != null && !remoteRecord.getPni().isPresent() && !remoteRecord.getNumber().isPresent()) { unregisteredAciOnly.add(remoteRecord); - } else if (remoteRecord.getServiceId() != null && remoteRecord.getServiceId().equals(remoteRecord.getPni().orElse(null))) { + } else if (remoteRecord.getAci() != null && remoteRecord.getAci().equals(remoteRecord.getPni().orElse(null))) { pniE164Only.add(remoteRecord); } } @@ -128,22 +127,19 @@ public void process(@NonNull Collection remoteRecords, @Non */ @Override boolean isInvalid(@NonNull SignalContactRecord remote) { - if (remote.getServiceId() == null) { + if (remote.getAci() == null) { Log.w(TAG, "No address on the ContentRecord -- marking as invalid."); return true; - } else if (remote.getServiceId().isUnknown()) { + } else if (remote.getAci().isUnknown()) { Log.w(TAG, "Found a ContactRecord without a UUID -- marking as invalid."); return true; - } else if (remote.getServiceId().equals(selfAci) || - remote.getServiceId().equals(selfPni) || + } else if (remote.getAci().equals(selfAci) || + remote.getAci().equals(selfPni) || (selfPni != null && selfPni.equals(remote.getPni().orElse(null))) || (selfE164 != null && remote.getNumber().isPresent() && remote.getNumber().get().equals(selfE164))) { Log.w(TAG, "Found a ContactRecord for ourselves -- marking as invalid."); return true; - } else if (!FeatureFlags.phoneNumberPrivacy() && remote.getServiceId().equals(remote.getPni().orElse(null))) { - Log.w(TAG, "Found a PNI-only ContactRecord when PNP is disabled -- marking as invalid."); - return true; } else if (remote.getNumber().isPresent() && !isValidE164(remote.getNumber().get())) { Log.w(TAG, "Found a record with an invalid E164. Marking as invalid."); return true; @@ -158,17 +154,13 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { remote = remote.withoutPni(); } - Optional found = recipientTable.getByServiceId(remote.getServiceId()); + Optional found = recipientTable.getByAci(remote.getAci()); - if (!found.isPresent() && remote.getNumber().isPresent()) { + if (found.isEmpty() && remote.getNumber().isPresent()) { found = recipientTable.getByE164(remote.getNumber().get()); } - if (!found.isPresent() && remote.getPni().isPresent()) { - found = recipientTable.getByServiceId(remote.getPni().get()); - } - - if (!found.isPresent() && remote.getPni().isPresent()) { + if (found.isEmpty() && remote.getPni().isPresent()) { found = recipientTable.getByPni(remote.getPni().get()); } @@ -219,8 +211,8 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { } if (identityKey != null && remote.getIdentityKey().isPresent() && !Arrays.equals(identityKey, remote.getIdentityKey().get())) { - Log.w(TAG, "The local and remote identity keys do not match for " + local.getServiceId() + ". Enqueueing a profile fetch."); - RetrieveProfileJob.enqueue(Recipient.trustedPush(local.getServiceId(), local.getPni().orElse(null), local.getNumber().orElse(null)).getId()); + Log.w(TAG, "The local and remote identity keys do not match for " + local.getAci() + ". Enqueueing a profile fetch."); + RetrieveProfileJob.enqueue(Recipient.trustedPush(local.getAci(), local.getPni().orElse(null), local.getNumber().orElse(null)).getId()); } PNI pni; @@ -259,7 +251,7 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { } byte[] unknownFields = remote.serializeUnknownFields(); - ServiceId serviceId = local.getServiceId() == ServiceId.UNKNOWN ? remote.getServiceId() : local.getServiceId(); + ACI aci = local.getAci() == ACI.UNKNOWN ? remote.getAci() : local.getAci(); byte[] profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null); String username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse(""); boolean blocked = remote.isBlocked(); @@ -273,15 +265,15 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { String systemGivenName = SignalStore.account().isPrimaryDevice() ? local.getSystemGivenName().orElse("") : remote.getSystemGivenName().orElse(""); String systemFamilyName = SignalStore.account().isPrimaryDevice() ? local.getSystemFamilyName().orElse("") : remote.getSystemFamilyName().orElse(""); String systemNickname = remote.getSystemNickname().orElse(""); - boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); - boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); + boolean matchesRemote = doParamsMatch(remote, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); + boolean matchesLocal = doParamsMatch(local, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); if (matchesRemote) { return remote; } else if (matchesLocal) { return local; } else { - return new SignalContactRecord.Builder(keyGenerator.generate(), serviceId, unknownFields) + return new SignalContactRecord.Builder(keyGenerator.generate(), aci, unknownFields) .setE164(e164) .setPni(pni) .setProfileGivenName(profileGivenName) @@ -317,7 +309,7 @@ void updateLocal(@NonNull StorageRecordUpdate update) { @Override public int compare(@NonNull SignalContactRecord lhs, @NonNull SignalContactRecord rhs) { - if (Objects.equals(lhs.getServiceId(), rhs.getServiceId()) || + if (Objects.equals(lhs.getAci(), rhs.getAci()) || (lhs.getNumber().isPresent() && Objects.equals(lhs.getNumber(), rhs.getNumber())) || (lhs.getPni().isPresent() && Objects.equals(lhs.getPni(), rhs.getPni()))) { @@ -355,7 +347,7 @@ private static boolean doParamsMatch(@NonNull SignalContactRecord contact, boolean hidden) { return Arrays.equals(contact.serializeUnknownFields(), unknownFields) && - Objects.equals(contact.getServiceId(), serviceId) && + Objects.equals(contact.getAci(), serviceId) && Objects.equals(contact.getPni().orElse(null), pni) && Objects.equals(contact.getNumber().orElse(null), e164) && Objects.equals(contact.getProfileGivenName().orElse(""), profileGivenName) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java index 09617f48b0..c1e08b3ff6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.subscription.Subscriber; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.storage.SignalAccountRecord; @@ -93,7 +94,7 @@ public static List localToRemotePinnedCo private static @NonNull SignalAccountRecord.PinnedConversation localToRemotePinnedConversation(@NonNull RecipientRecord settings) { switch (settings.getGroupType()) { - case NONE : return SignalAccountRecord.PinnedConversation.forContact(new SignalServiceAddress(settings.getServiceId(), settings.getE164())); + case NONE : return SignalAccountRecord.PinnedConversation.forContact(new SignalServiceAddress(settings.getAci(), settings.getE164())); case SIGNAL_V1: return SignalAccountRecord.PinnedConversation.forGroupV1(settings.getGroupId().requireV1().getDecodedId()); case SIGNAL_V2: return SignalAccountRecord.PinnedConversation.forGroupV2(settings.getSyncExtras().getGroupMasterKey().serialize()); default : throw new AssertionError("Unexpected group type!"); @@ -101,14 +102,14 @@ public static List localToRemotePinnedCo } private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientRecord recipient, byte[] rawStorageId) { - if (recipient.getServiceId() == null && recipient.getE164() == null) { + if (recipient.getAci() == null && recipient.getE164() == null) { throw new AssertionError("Must have either a UUID or a phone number!"); } - ServiceId serviceId = recipient.getServiceId() != null ? recipient.getServiceId() : ServiceId.UNKNOWN; + ACI aci = recipient.getAci() != null ? recipient.getAci() : ACI.UNKNOWN; boolean hideStory = recipient.getExtras() != null && recipient.getExtras().hideStory(); - return new SignalContactRecord.Builder(rawStorageId, serviceId, recipient.getSyncExtras().getStorageProto()) + return new SignalContactRecord.Builder(rawStorageId, aci, recipient.getSyncExtras().getStorageProto()) .setE164(recipient.getE164()) .setPni(recipient.getPni()) .setProfileKey(recipient.getProfileKey()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java index c58b7d42a8..3cacde284f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java @@ -164,7 +164,7 @@ private static void validateManifestAndInserts(@NonNull SignalStorageManifest ma if (insert.getContact().isPresent()) { SignalContactRecord contact = insert.getContact().get(); - if (self.requireServiceId().equals(contact.getServiceId()) || + if (self.requireAci().equals(contact.getAci()) || self.requirePni().equals(contact.getPni().orElse(null)) || self.requireE164().equals(contact.getNumber().orElse(""))) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java index 2d1ca94c84..38dc886a4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java @@ -8,15 +8,11 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.mms.PushMediaConstraints; -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.recipients.Recipient; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Optional; -import java.util.Set; /** * Provide access to locale specific values within feature flags following the locale CSV-Colon format. @@ -85,7 +81,7 @@ private static boolean isEnabled(@NonNull String flag, @NonNull String serialize } long countEnabled = getCountryValue(countryCodeValues, self.getE164().orElse(""), 0); - long currentUserBucket = BucketingUtil.bucket(flag, self.requireServiceId().uuid(), 1_000_000); + long currentUserBucket = BucketingUtil.bucket(flag, self.requireAci().getRawUuid(), 1_000_000); return countEnabled > currentUserBucket; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java index 851f0f343b..5ee2ae2d98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java @@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.util.Base64UrlSafe; diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifySafetyNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifySafetyNumberViewModel.kt index 17011eb96c..21787cb9c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifySafetyNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifySafetyNumberViewModel.kt @@ -63,9 +63,9 @@ class VerifySafetyNumberViewModel( e164Fingerprint = SafetyNumberFingerprint(version, localIdentifier, localIdentity, remoteIdentifier, remoteIdentity, generator.createFor(version, localIdentifier, localIdentity, remoteIdentifier, remoteIdentity)) } - if (resolved.serviceId.isPresent) { + if (resolved.aci.isPresent) { val localIdentifier = SignalStore.account().requireAci().toByteArray() - val remoteIdentifier = resolved.requireServiceId().toByteArray() + val remoteIdentifier = resolved.requireAci().toByteArray() val version = 2 aciFingerprint = SafetyNumberFingerprint(version, localIdentifier, localIdentity, remoteIdentifier, remoteIdentity, generator.createFor(version, localIdentifier, localIdentity, remoteIdentifier, remoteIdentity)) } @@ -97,13 +97,13 @@ class VerifySafetyNumberViewModel( val context: Context = ApplicationDependencies.getApplication() SignalExecutors.BOUNDED.execute { - ReentrantSessionLock.INSTANCE.acquire().use { unused -> + ReentrantSessionLock.INSTANCE.acquire().use { _ -> if (verified) { Log.i(TAG, "Saving identity: $recipientId") ApplicationDependencies.getProtocolStore().aci().identities() .saveIdentityWithoutSideEffects( recipientId, - recipient.resolve().requireServiceId(), + recipient.resolve().requireAci(), remoteIdentity, IdentityTable.VerifiedStatus.VERIFIED, false, diff --git a/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt index b4d6b84048..bf03432844 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt @@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.thoughtcrime.securesms.util.IdentityUtil -import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import org.whispersystems.signalservice.api.services.ProfileService import org.whispersystems.signalservice.internal.ServiceResponse @@ -125,7 +124,7 @@ class SafetyNumberRepositoryTest { @Test fun batchSafetyNumberCheckSync_batchOf1_oneChange() { val other = recipientPool[1] - val otherAci = ACI.from(other.requireServiceId()) + val otherAci = other.requireAci() val otherNewIdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey val keys = listOf(ContactSearchKey.RecipientSearchKey(other.id, false)) @@ -146,7 +145,7 @@ class SafetyNumberRepositoryTest { fun batchSafetyNumberCheckSync_batchOf2_oneChange() { val other = recipientPool[1] val secondOther = recipientPool[2] - val otherAci = ACI.from(other.requireServiceId()) + val otherAci = other.requireAci() val otherNewIdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey val keys = listOf(ContactSearchKey.RecipientSearchKey(other.id, false), ContactSearchKey.RecipientSearchKey(secondOther.id, false)) diff --git a/app/src/test/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelperTest.java b/app/src/test/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelperTest.java index 11bedc77eb..239b50b8bf 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelperTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/contacts/sync/FuzzyPhoneNumberHelperTest.java @@ -3,7 +3,7 @@ import org.junit.Test; import org.thoughtcrime.securesms.contacts.sync.FuzzyPhoneNumberHelper.InputResult; import org.thoughtcrime.securesms.contacts.sync.FuzzyPhoneNumberHelper.OutputResult; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Arrays; import java.util.Collections; diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt b/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt index 52317406ae..d82bf1c830 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt @@ -85,15 +85,15 @@ class GroupChangeData(private val revision: Int, private val groupOperations: Gr } fun source(serviceId: ServiceId) { - actionsBuilder.sourceUuid = groupOperations.encryptUuid(serviceId.uuid()) + actionsBuilder.sourceUuid = groupOperations.encryptUuid(serviceId.rawUuid) } fun deleteMember(serviceId: ServiceId) { - actionsBuilder.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(groupOperations.encryptUuid(serviceId.uuid()))) + actionsBuilder.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(groupOperations.encryptUuid(serviceId.rawUuid))) } fun modifyRole(serviceId: ServiceId, role: Member.Role) { - actionsBuilder.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(groupOperations.encryptUuid(serviceId.uuid())).setRole(role)) + actionsBuilder.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(groupOperations.encryptUuid(serviceId.rawUuid)).setRole(role)) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/RecipientDatabaseTestUtils.kt b/app/src/test/java/org/thoughtcrime/securesms/database/RecipientDatabaseTestUtils.kt index 4da48710d2..428576af70 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/RecipientDatabaseTestUtils.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/database/RecipientDatabaseTestUtils.kt @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientDetails import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.wallpaper.ChatWallpaper -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import java.util.Optional import java.util.UUID import kotlin.random.Random @@ -34,7 +34,7 @@ object RecipientDatabaseTestUtils { isSelf: Boolean = false, participants: List = listOf(), recipientId: RecipientId = RecipientId.from(Random.nextLong()), - serviceId: ServiceId? = ServiceId.from(UUID.randomUUID()), + serviceId: ACI? = ACI.from(UUID.randomUUID()), username: String? = null, e164: String? = null, email: String? = null, diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java index 68b6ac0a76..c0c7cba479 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java @@ -26,8 +26,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -88,8 +88,8 @@ public void setup() { producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), new ServiceIds(ACI.from(you), PNI.from(UUID.randomUUID())), null); - recipientIdMockedStatic.when(() -> RecipientId.from(ServiceId.from(alice))).thenReturn(aliceId); - recipientIdMockedStatic.when(() -> RecipientId.from(ServiceId.from(bob))).thenReturn(bobId); + recipientIdMockedStatic.when(() -> RecipientId.from(ACI.from(alice))).thenReturn(aliceId); + recipientIdMockedStatic.when(() -> RecipientId.from(ACI.from(bob))).thenReturn(bobId); recipientMockedStatic.when(() -> Recipient.resolved(aliceId)).thenReturn(aliceRecipient); recipientMockedStatic.when(() -> Recipient.resolved(bobId)).thenReturn(bobRecipient); } @@ -1446,7 +1446,7 @@ private static GroupStateBuilder newGroupBy(UUID foundingMember, int revision) { } private void assertSingleChangeMentioning(DecryptedGroupChange change, List expectedMentions) { - List expectedMentionSids = expectedMentions.stream().map(ServiceId::from).collect(Collectors.toList()); + List expectedMentionSids = expectedMentions.stream().map(ACI::from).collect(Collectors.toList()); List changes = producer.describeChanges(null, change); diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java index c3949c8eb0..9308ee02b6 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java @@ -7,6 +7,7 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.Arrays; @@ -46,7 +47,7 @@ public void staticDescription_byString() { @Test(expected = UnsupportedOperationException.class) public void stringFactory_cannot_call_static_string() { - UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), () -> new SpannableString("update"), 0); + UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), () -> new SpannableString("update"), 0); description.getStaticSpannable(); } @@ -60,7 +61,7 @@ public void stringFactory_not_evaluated_until_getString() { return new SpannableString("update"); }; - UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0); + UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory, 0); assertEquals(0, factoryCalls.get()); @@ -74,7 +75,7 @@ public void stringFactory_not_evaluated_until_getString() { public void stringFactory_reevaluated_on_every_call() { AtomicInteger factoryCalls = new AtomicInteger(); UpdateDescription.SpannableFactory stringFactory = () -> new SpannableString( "call" + factoryCalls.incrementAndGet()); - UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0); + UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory, 0); assertEquals("call1", description.getSpannable().toString()); assertEquals("call2", description.getSpannable().toString()); @@ -108,8 +109,8 @@ public void concat_dynamic_lines() { AtomicInteger factoryCalls2 = new AtomicInteger(); UpdateDescription.SpannableFactory stringFactory1 = () -> new SpannableString("update." + factoryCalls1.incrementAndGet()); UpdateDescription.SpannableFactory stringFactory2 = () -> new SpannableString("update." + factoryCalls2.incrementAndGet()); - UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0); - UpdateDescription description2 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0); + UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory1, 0); + UpdateDescription description2 = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory2, 0); factoryCalls1.set(10); factoryCalls2.set(20); @@ -129,9 +130,9 @@ public void concat_dynamic_lines_and_static_lines() { AtomicInteger factoryCalls2 = new AtomicInteger(); UpdateDescription.SpannableFactory stringFactory1 = () -> new SpannableString("update." + factoryCalls1.incrementAndGet()); UpdateDescription.SpannableFactory stringFactory2 = () -> new SpannableString("update." + factoryCalls2.incrementAndGet()); - UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0); + UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory1, 0); UpdateDescription description2 = UpdateDescription.staticDescription("static", 0); - UpdateDescription description3 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0); + UpdateDescription description3 = UpdateDescription.mentioning(Collections.singletonList(ACI.from(UUID.randomUUID())), stringFactory2, 0); factoryCalls1.set(100); factoryCalls2.set(200); diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt index 932a464f40..0302fc6db7 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt @@ -39,9 +39,8 @@ import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceIds import java.util.UUID @@ -58,9 +57,9 @@ class GroupManagerV2Test_edit { val selfAci: ACI = ACI.from(UUID.randomUUID()) val selfPni: PNI = PNI.from(UUID.randomUUID()) val serviceIds: ServiceIds = ServiceIds(selfAci, selfPni) - val otherSid: ServiceId = ServiceId.from(UUID.randomUUID()) - val selfAndOthers: List = listOf(member(selfAci), member(otherSid)) - val others: List = listOf(member(otherSid)) + val otherAci: ACI = ACI.from(UUID.randomUUID()) + val selfAndOthers: List = listOf(member(selfAci), member(otherAci)) + val others: List = listOf(member(otherAci)) } private lateinit var groupTable: GroupTable @@ -136,13 +135,13 @@ class GroupManagerV2Test_edit { revision = 5, members = listOf( member(selfAci, role = Member.Role.ADMINISTRATOR), - member(otherSid) + member(otherAci) ) ) groupChange(6) { source(selfAci) deleteMember(selfAci) - modifyRole(otherSid, Member.Role.ADMINISTRATOR) + modifyRole(otherAci, Member.Role.ADMINISTRATOR) } } @@ -153,7 +152,7 @@ class GroupManagerV2Test_edit { then { patchedGroup -> assertThat("Revision updated by one", patchedGroup.revision, `is`(6)) assertThat("Self is no longer in the group", patchedGroup.membersList.find { it.uuid == selfAci.toByteString() }, Matchers.nullValue()) - assertThat("Other is now an admin in the group", patchedGroup.membersList.find { it.uuid == otherSid.toByteString() }?.role, `is`(Member.Role.ADMINISTRATOR)) + assertThat("Other is now an admin in the group", patchedGroup.membersList.find { it.uuid == otherAci.toByteString() }?.role, `is`(Member.Role.ADMINISTRATOR)) } } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java index 1c69223d74..8928b335a8 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java @@ -5,6 +5,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.testutil.LogRecorder; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.Collections; @@ -40,7 +41,7 @@ public void new_member_is_not_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(editor).addMember(newMember, profileKey).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(newMember), profileKey))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(newMember), profileKey))); } @Test @@ -52,7 +53,7 @@ public void new_member_by_self_is_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(newMember).addMember(newMember, profileKey).build()); assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(newMember), profileKey))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(newMember), profileKey))); } @Test @@ -64,7 +65,7 @@ public void new_member_by_self_promote_is_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(newMember).promote(newMember, profileKey).build()); assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(newMember), profileKey))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(newMember), profileKey))); } @Test @@ -77,7 +78,7 @@ public void new_member_by_promote_by_other_editor_is_not_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(editor).promote(newMember, profileKey).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(newMember), profileKey))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(newMember), profileKey))); } @Test @@ -89,7 +90,7 @@ public void new_member_by_promote_by_unknown_editor_is_not_authoritative() { profileKeySet.addKeysFromGroupChange(changeByUnknown().promote(newMember, profileKey).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(newMember), profileKey))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(newMember), profileKey))); } @Test @@ -101,7 +102,7 @@ public void profile_key_update_by_self_is_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(member).profileKeyUpdate(member, profileKey).build()); assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(member), profileKey))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(member), profileKey))); } @Test @@ -114,7 +115,7 @@ public void profile_key_update_by_another_is_not_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(member), profileKey))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(member), profileKey))); } @Test @@ -129,7 +130,7 @@ public void multiple_updates_overwrite() { profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey2).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(member), profileKey2))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(member), profileKey2))); } @Test @@ -144,7 +145,7 @@ public void authoritative_takes_priority_when_seen_first() { profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey2).build()); assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(member), profileKey1))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(member), profileKey1))); } @Test @@ -159,7 +160,7 @@ public void authoritative_takes_priority_when_seen_second() { profileKeySet.addKeysFromGroupChange(changeBy(member).profileKeyUpdate(member, profileKey2).build()); assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(member), profileKey2))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(member), profileKey2))); } @Test @@ -186,7 +187,7 @@ public void new_requesting_member_if_editor_is_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(editor).requestJoin(profileKey).build()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ServiceId.from(editor), profileKey))); + assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(ACI.from(editor), profileKey))); assertTrue(profileKeySet.getProfileKeys().isEmpty()); } @@ -200,6 +201,6 @@ public void new_requesting_member_if_not_editor_is_not_authoritative() { profileKeySet.addKeysFromGroupChange(changeBy(editor).requestJoin(requesting, profileKey).build()); assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ServiceId.from(requesting), profileKey))); + assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(ACI.from(requesting), profileKey))); } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt index 10433cb769..ec3d1e9249 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt @@ -46,9 +46,8 @@ import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceIds import java.util.UUID @@ -60,9 +59,9 @@ class GroupsV2StateProcessorTest { private val masterKey = GroupMasterKey(fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) private val selfAci: ACI = ACI.from(UUID.randomUUID()) private val serviceIds: ServiceIds = ServiceIds(selfAci, PNI.from(UUID.randomUUID())) - private val otherSid: ServiceId = ServiceId.from(UUID.randomUUID()) - private val selfAndOthers: List = listOf(member(selfAci), member(otherSid)) - private val others: List = listOf(member(otherSid)) + private val otherAci: ACI = ACI.from(UUID.randomUUID()) + private val selfAndOthers: List = listOf(member(selfAci), member(otherAci)) + private val others: List = listOf(member(otherAci)) } private lateinit var groupTable: GroupTable @@ -268,7 +267,7 @@ class GroupsV2StateProcessorTest { revision = 2, title = "Breaking Signal for Science", description = "We break stuff, because we must.", - members = listOf(member(otherSid), member(selfAci, joinedAt = 2)) + members = listOf(member(otherAci), member(selfAci, joinedAt = 2)) ) changeSet { changeLog(2) { @@ -290,7 +289,7 @@ class GroupsV2StateProcessorTest { revision = 3, title = "Breaking Signal for Science", description = "We break stuff, because we must.", - members = listOf(member(otherSid), member(selfAci, joinedAt = 2)) + members = listOf(member(otherAci), member(selfAci, joinedAt = 2)) ) changeSet { changeLog(2) { @@ -346,7 +345,7 @@ class GroupsV2StateProcessorTest { serverState( revision = 3, title = "Beam me up", - members = listOf(member(otherSid), member(selfAci, joinedAt = 3)) + members = listOf(member(otherAci), member(selfAci, joinedAt = 3)) ) changeSet { changeLog(3) { @@ -376,7 +375,7 @@ class GroupsV2StateProcessorTest { serverState( revision = 5, title = "Beam me up!", - members = listOf(member(otherSid), member(selfAci, joinedAt = 3)) + members = listOf(member(otherAci), member(selfAci, joinedAt = 3)) ) changeSet { changeLog(3) { @@ -470,7 +469,7 @@ class GroupsV2StateProcessorTest { */ @Test fun missedMemberAddResolvesWithMultipleRevisionUpdate() { - val secondOther = member(ServiceId.from(UUID.randomUUID())) + val secondOther = member(ACI.from(UUID.randomUUID())) profileAndMessageHelper.masterKey = masterKey @@ -521,7 +520,7 @@ class GroupsV2StateProcessorTest { */ @Test fun missedMemberAddResolvesWithForcedUpdate() { - val secondOther = member(ServiceId.from(UUID.randomUUID())) + val secondOther = member(ACI.from(UUID.randomUUID())) profileAndMessageHelper.masterKey = masterKey diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java index 23b84b782f..57479aa402 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.testutil.LogRecorder; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import java.util.Optional; @@ -40,7 +41,7 @@ public void empty_access_by_nulls() { @Test public void empty_access_by_uuid() { - RecipientId recipientId = recipientIdCache.get(ServiceId.from(UUID.randomUUID()), null); + RecipientId recipientId = recipientIdCache.get(ACI.from(UUID.randomUUID()), null); assertNull(recipientId); } @@ -55,7 +56,7 @@ public void empty_access_by_e164() { @Test public void cache_hit_by_uuid() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); @@ -67,8 +68,8 @@ public void cache_hit_by_uuid() { @Test public void cache_miss_by_uuid() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); - ServiceId sid2 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); + ServiceId sid2 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); @@ -80,7 +81,7 @@ public void cache_miss_by_uuid() { @Test public void cache_hit_by_uuid_e164_not_supplied_on_get() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")); @@ -92,7 +93,7 @@ public void cache_hit_by_uuid_e164_not_supplied_on_get() { @Test public void cache_miss_by_uuid_e164_not_supplied_on_put() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); @@ -129,7 +130,7 @@ public void cache_miss_by_e164() { @Test public void cache_hit_by_e164_uuid_not_supplied_on_get() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")); @@ -141,7 +142,7 @@ public void cache_hit_by_e164_uuid_not_supplied_on_get() { @Test public void cache_miss_by_e164_uuid_not_supplied_on_put() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); String e164 = "+1555123456"; recipientIdCache.put(recipient(recipientId1, null, e164)); @@ -154,7 +155,7 @@ public void cache_miss_by_e164_uuid_not_supplied_on_put() { @Test public void cache_hit_by_both() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); String e164 = "+1555123456"; recipientIdCache.put(recipient(recipientId1, sid1, e164)); @@ -167,7 +168,7 @@ public void cache_hit_by_both() { @Test public void full_recipient_id_learned_by_two_puts() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); String e164 = "+1555123456"; recipientIdCache.put(recipient(recipientId1, sid1, null)); @@ -182,7 +183,7 @@ public void full_recipient_id_learned_by_two_puts() { public void if_cache_state_disagrees_returns_null() { RecipientId recipientId1 = recipientId(); RecipientId recipientId2 = recipientId(); - ServiceId sid = ServiceId.from(UUID.randomUUID()); + ServiceId sid = ACI.from(UUID.randomUUID()); String e164 = "+1555123456"; recipientIdCache.put(recipient(recipientId1, null, e164)); @@ -200,7 +201,7 @@ public void if_cache_state_disagrees_returns_null() { public void after_invalid_cache_hit_entries_are_cleared_up() { RecipientId recipientId1 = recipientId(); RecipientId recipientId2 = recipientId(); - ServiceId sid = ServiceId.from(UUID.randomUUID()); + ServiceId sid = ACI.from(UUID.randomUUID()); String e164 = "+1555123456"; recipientIdCache.put(recipient(recipientId1, null, e164)); @@ -216,8 +217,8 @@ public void after_invalid_cache_hit_entries_are_cleared_up() { public void multiple_entries() { RecipientId recipientId1 = recipientId(); RecipientId recipientId2 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); - ServiceId sid2 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); + ServiceId sid2 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); recipientIdCache.put(recipient(recipientId2, sid2, null)); @@ -229,12 +230,12 @@ public void multiple_entries() { @Test public void drops_oldest_when_reaches_cache_limit() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); for (int i = 0; i < TEST_CACHE_LIMIT; i++) { - recipientIdCache.put(recipient(recipientId(), ServiceId.from(UUID.randomUUID()), null)); + recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); } assertNull(recipientIdCache.get(sid1, null)); @@ -243,17 +244,17 @@ public void drops_oldest_when_reaches_cache_limit() { @Test public void remains_in_cache_when_used_before_reaching_cache_limit() { RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ServiceId.from(UUID.randomUUID()); + ServiceId sid1 = ACI.from(UUID.randomUUID()); recipientIdCache.put(recipient(recipientId1, sid1, null)); for (int i = 0; i < TEST_CACHE_LIMIT - 1; i++) { - recipientIdCache.put(recipient(recipientId(), ServiceId.from(UUID.randomUUID()), null)); + recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); } assertEquals(recipientId1, recipientIdCache.get(sid1, null)); - recipientIdCache.put(recipient(recipientId(), ServiceId.from(UUID.randomUUID()), null)); + recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); assertEquals(recipientId1, recipientIdCache.get(sid1, null)); } diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt index 8cf104b635..c754f9bdf6 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt @@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.keyvalue.AccountValues import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.testutil.EmptyLogger import org.thoughtcrime.securesms.util.FeatureFlags -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.storage.SignalContactRecord import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.internal.storage.protos.ContactRecord @@ -56,9 +56,9 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServicePni(PNI_B.toString()) - setServiceE164(E164_B) + setAci(ACI_B.toString()) + setPni(PNI_B.toString()) + setE164(E164_B) } // WHEN @@ -74,7 +74,7 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceE164(E164_B) + setE164(E164_B) } // WHEN @@ -90,8 +90,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164(E164_A) + setAci(ACI_B.toString()) + setE164(E164_A) } // WHEN @@ -107,7 +107,7 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_A.toString()) + setAci(ACI_A.toString()) } // WHEN @@ -123,7 +123,7 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(PNI_A.toString()) + setAci(PNI_A.toString()) } // WHEN @@ -139,8 +139,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServicePni(PNI_A.toString()) + setAci(ACI_B.toString()) + setPni(PNI_A.toString()) } // WHEN @@ -150,52 +150,14 @@ class ContactRecordProcessorTest { assertTrue(result) } - @Test - fun `isInvalid, pniOnly pnpDisabled, true`() { - // GIVEN - val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) - - featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(false) - - val record = buildRecord { - setServiceId(PNI_B.toString()) - setServicePni(PNI_B.toString()) - } - - // WHEN - val result = subject.isInvalid(record) - - // THEN - assertTrue(result) - } - - @Test - fun `isInvalid, pniOnly pnpEnabled, false`() { - // GIVEN - val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) - - featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(true) - - val record = buildRecord { - setServiceId(PNI_B.toString()) - setServicePni(PNI_B.toString()) - } - - // WHEN - val result = subject.isInvalid(record) - - // THEN - assertFalse(result) - } - @Test fun `isInvalid, valid E164, true`() { // GIVEN val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164(E164_B) + setAci(ACI_B.toString()) + setE164(E164_B) } // WHEN @@ -211,8 +173,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164("15551234567") + setAci(ACI_B.toString()) + setE164("15551234567") } // WHEN @@ -228,8 +190,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164("+1555ABC4567") + setAci(ACI_B.toString()) + setE164("+1555ABC4567") } // WHEN @@ -245,8 +207,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164("+") + setAci(ACI_B.toString()) + setE164("+") } // WHEN @@ -262,8 +224,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164("+12345678901234567890") + setAci(ACI_B.toString()) + setE164("+12345678901234567890") } // WHEN @@ -279,8 +241,8 @@ class ContactRecordProcessorTest { val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) val record = buildRecord { - setServiceId(ACI_B.toString()) - setServiceE164("+05551234567") + setAci(ACI_B.toString()) + setE164("+05551234567") } // WHEN @@ -298,22 +260,22 @@ class ContactRecordProcessorTest { featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(true) val local = buildRecord(STORAGE_ID_A) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_A) - setServicePni(PNI_A.toString()) + setAci(ACI_A.toString()) + setE164(E164_A) + setPni(PNI_A.toString()) } val remote = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_A) - setServicePni(PNI_B.toString()) + setAci(ACI_A.toString()) + setE164(E164_A) + setPni(PNI_B.toString()) } // WHEN val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C)) // THEN - assertEquals(local.serviceId, result.serviceId) + assertEquals(local.aci, result.aci) assertEquals(local.number.get(), result.number.get()) assertEquals(local.pni.get(), result.pni.get()) } @@ -326,22 +288,22 @@ class ContactRecordProcessorTest { featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(true) val local = buildRecord(STORAGE_ID_A) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_A) - setServicePni(PNI_A.toString()) + setAci(ACI_A.toString()) + setE164(E164_A) + setPni(PNI_A.toString()) } val remote = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_B) - setServicePni(PNI_A.toString()) + setAci(ACI_A.toString()) + setE164(E164_B) + setPni(PNI_A.toString()) } // WHEN val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C)) // THEN - assertEquals(local.serviceId, result.serviceId) + assertEquals(local.aci, result.aci) assertEquals(local.number.get(), result.number.get()) assertEquals(local.pni.get(), result.pni.get()) } @@ -354,22 +316,22 @@ class ContactRecordProcessorTest { featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(true) val local = buildRecord(STORAGE_ID_A) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_A) - setServicePni(PNI_A.toString()) + setAci(ACI_A.toString()) + setE164(E164_A) + setPni(PNI_A.toString()) } val remote = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_B) - setServicePni(PNI_B.toString()) + setAci(ACI_A.toString()) + setE164(E164_B) + setPni(PNI_B.toString()) } // WHEN val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C)) // THEN - assertEquals(remote.serviceId, result.serviceId) + assertEquals(remote.aci, result.aci) assertEquals(remote.number.get(), result.number.get()) assertEquals(remote.pni.get(), result.pni.get()) } @@ -382,22 +344,22 @@ class ContactRecordProcessorTest { featureFlags.`when` { FeatureFlags.phoneNumberPrivacy() }.thenReturn(false) val local = buildRecord(STORAGE_ID_A) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_A) - setServicePni(PNI_A.toString()) + setAci(ACI_A.toString()) + setE164(E164_A) + setPni(PNI_A.toString()) } val remote = buildRecord(STORAGE_ID_B) { - setServiceId(ACI_A.toString()) - setServiceE164(E164_B) - setServicePni(PNI_B.toString()) + setAci(ACI_A.toString()) + setE164(E164_B) + setPni(PNI_B.toString()) } // WHEN val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C)) // THEN - assertEquals(remote.serviceId, result.serviceId) + assertEquals(remote.aci, result.aci) assertEquals(remote.number.get(), result.number.get()) assertEquals(false, result.pni.isPresent) } diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java index bbdfc1bf10..3b4fb873af 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult; import org.thoughtcrime.securesms.util.FeatureFlags; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.storage.SignalAccountRecord; import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; @@ -40,15 +41,15 @@ public final class StorageSyncHelperTest { - private static final ServiceId SID_A = ServiceId.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); - private static final ServiceId SID_SELF = ServiceId.parseOrThrow("1b2a2ca5-fc9e-4656-8c9f-22cc349ed3af"); + private static final ACI ACI_A = ACI.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); + private static final ACI ACI_SELF = ACI.parseOrThrow("1b2a2ca5-fc9e-4656-8c9f-22cc349ed3af"); private static final String E164_A = "+16108675309"; private static final String E164_SELF = "+16105555555"; private static final Recipient SELF = mock(Recipient.class); static { - when(SELF.getServiceId()).thenReturn(Optional.of(SID_SELF)); + when(SELF.getServiceId()).thenReturn(Optional.of(ACI_SELF)); when(SELF.getE164()).thenReturn(Optional.of(E164_SELF)); when(SELF.resolve()).thenReturn(SELF); } @@ -132,8 +133,8 @@ public void ContactUpdate_equals_sameProfileKeys() { byte[] profileKey = new byte[32]; byte[] profileKeyCopy = profileKey.clone(); - SignalContactRecord a = contactBuilder(1, SID_A, E164_A, "a").setProfileKey(profileKey).build(); - SignalContactRecord b = contactBuilder(1, SID_A, E164_A, "a").setProfileKey(profileKeyCopy).build(); + SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setProfileKey(profileKey).build(); + SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setProfileKey(profileKeyCopy).build(); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); @@ -147,8 +148,8 @@ public void ContactUpdate_equals_differentProfileKeys() { byte[] profileKeyCopy = profileKey.clone(); profileKeyCopy[0] = 1; - SignalContactRecord a = contactBuilder(1, SID_A, E164_A, "a").setProfileKey(profileKey).build(); - SignalContactRecord b = contactBuilder(1, SID_A, E164_A, "a").setProfileKey(profileKeyCopy).build(); + SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setProfileKey(profileKey).build(); + SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setProfileKey(profileKeyCopy).build(); assertNotEquals(a, b); assertNotEquals(a.hashCode(), b.hashCode()); @@ -171,7 +172,7 @@ private static SignalStorageRecord record(SignalRecord record) { } private static SignalContactRecord.Builder contactBuilder(int key, - ServiceId aci, + ACI aci, String e164, String profileName) { diff --git a/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt b/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt index 29ff559c15..b6ff71b053 100644 --- a/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt +++ b/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt @@ -8,6 +8,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember import org.signal.storageservice.protos.groups.local.DecryptedPendingMember import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.SignalServiceProtos import java.util.UUID @@ -30,30 +31,30 @@ fun encryptedGroupContext(masterKey: GroupMasterKey): SignalServiceProtos.GroupC return SignalServiceProtos.GroupContextV2.newBuilder().setMasterKey(ByteString.copyFrom(masterKey.serialize())).build() } -fun DecryptedGroupChange.Builder.addRequestingMember(serviceId: ServiceId) { - addNewRequestingMembers(requestingMember(serviceId)) +fun DecryptedGroupChange.Builder.addRequestingMember(aci: ACI) { + addNewRequestingMembers(requestingMember(aci)) } -fun DecryptedGroupChange.Builder.deleteRequestingMember(serviceId: ServiceId) { - addDeleteRequestingMembers(serviceId.toByteString()) +fun DecryptedGroupChange.Builder.deleteRequestingMember(aci: ACI) { + addDeleteRequestingMembers(aci.toByteString()) } -fun DecryptedGroupChange.Builder.addMember(serviceId: ServiceId) { - addNewMembers(member(serviceId)) +fun DecryptedGroupChange.Builder.addMember(aci: ACI) { + addNewMembers(member(aci)) } fun ServiceId.toByteString(): ByteString { - return UuidUtil.toByteString(uuid()) + return UuidUtil.toByteString(rawUuid) } fun member(serviceId: UUID, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0): DecryptedMember { - return member(ServiceId.from(serviceId), role, joinedAt) + return member(ACI.from(serviceId), role, joinedAt) } -fun member(serviceId: ServiceId, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0): DecryptedMember { +fun member(aci: ACI, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0): DecryptedMember { return DecryptedMember.newBuilder() .setRole(role) - .setUuid(serviceId.toByteString()) + .setUuid(aci.toByteString()) .setJoinedAtRevision(joinedAt) .build() } diff --git a/dependencies.gradle b/dependencies.gradle index 5ef1e5ec1e..eb4caa57a9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -14,7 +14,7 @@ dependencyResolutionManagement { version('exoplayer', '2.19.0') version('glide', '4.13.2') version('kotlin', '1.8.10') - version('libsignal-client', '0.26.0') + version('libsignal-client', '0.28.1') version('mp4parser', '1.9.39') version('android-gradle-plugin', '8.0.2') version('accompanist', '0.28.0') diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 85a8748577..15f8e75895 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -5000,6 +5000,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -5008,6 +5024,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 605283f91c..117f73b1fb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -33,8 +33,8 @@ import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.NoContentException; @@ -740,14 +740,14 @@ public Optional setVersionedProfile(ACI aci, new ProfileCipherOutputStreamFactory(profileKey)); } - return this.pushServiceSocket.writeProfile(new SignalServiceProfileWrite(profileKey.getProfileKeyVersion(aci.uuid()).serialize(), + return this.pushServiceSocket.writeProfile(new SignalServiceProfileWrite(profileKey.getProfileKeyVersion(aci.getRawUuid()).serialize(), ciphertextName, ciphertextAbout, ciphertextEmoji, ciphertextMobileCoinAddress, avatar.hasAvatar, avatar.keepTheSame, - profileKey.getCommitment(aci.uuid()).serialize(), + profileKey.getCommitment(aci.getRawUuid()).serialize(), visibleBadgeIds), profileAvatarData); } @@ -756,7 +756,7 @@ public Optional resolveProfileKeyCredential(Servic throws NonSuccessfulResponseCodeException, PushNetworkException { try { - ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId.uuid(), profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS); + ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId.getRawUuid(), profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS); return credential.getExpiringProfileKeyCredential(); } catch (InterruptedException | TimeoutException e) { throw new PushNetworkException(e); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 565f81fe4b..61eda77ff6 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -100,9 +100,9 @@ public ListenableFuture retrieveProfile(SignalServiceAddre if (profileKey.isPresent()) { if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { - return socket.retrieveVersionedProfileAndCredential(serviceId.uuid(), profileKey.get(), unidentifiedAccess, locale); + return socket.retrieveVersionedProfileAndCredential(serviceId.getRawUuid(), profileKey.get(), unidentifiedAccess, locale); } else { - return FutureTransformers.map(socket.retrieveVersionedProfile(serviceId.uuid(), profileKey.get(), unidentifiedAccess, locale), profile -> { + return FutureTransformers.map(socket.retrieveVersionedProfile(serviceId.getRawUuid(), profileKey.get(), unidentifiedAccess, locale), profile -> { return new ProfileAndCredential(profile, SignalServiceProfile.RequestType.PROFILE, Optional.empty()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index b07b1bcb13..10e04f6535 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -65,7 +65,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage; import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.DistributionId; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; @@ -890,7 +890,7 @@ private SignalServiceProtos.PniSignatureMessage createPniSignatureMessage() { byte[] signature = localPniIdentity.signAlternateIdentity(aciStore.getIdentityKeyPair().getPublicKey()); return SignalServiceProtos.PniSignatureMessage.newBuilder() - .setPni(UuidUtil.toByteString(localPni.uuid())) + .setPni(UuidUtil.toByteString(localPni.getRawUuid())) .setSignature(ByteString.copyFrom(signature)) .build(); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index 81cb50ccc6..3fe1b10b48 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -47,7 +47,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceMetadata; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -102,7 +102,7 @@ public byte[] encryptForGroup(DistributionId distributionId, PushTransportDetails transport = new PushTransportDetails(); SignalProtocolAddress localProtocolAddress = new SignalProtocolAddress(localAddress.getIdentifier(), localDeviceId); SignalGroupCipher groupCipher = new SignalGroupCipher(sessionLock, new GroupCipher(signalProtocolStore, localProtocolAddress)); - SignalSealedSessionCipher sessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId)); + SignalSealedSessionCipher sessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId)); CiphertextMessage message = groupCipher.encrypt(distributionId.asUuid(), transport.getPaddedMessageBody(unpaddedMessage)); UnidentifiedSenderMessageContent messageContent = new UnidentifiedSenderMessageContent(message, senderCertificate, @@ -119,7 +119,7 @@ public OutgoingPushMessage encrypt(SignalProtocolAddress destination, { if (unidentifiedAccess.isPresent()) { SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, destination)); - SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId)); + SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId)); return content.processSealedSender(sessionCipher, sealedSessionCipher, destination, unidentifiedAccess.get().getUnidentifiedCertificate()); } else { @@ -231,7 +231,7 @@ private Plaintext decryptInternal(Envelope envelope, long serverDeliveredTimesta paddedMessage = new PlaintextContent(envelope.getContent().toByteArray()).getBody(); metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationServiceId()); } else if (envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE) { - SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId)); + SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId)); DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.getContent().toByteArray(), envelope.getServerTimestamp()); SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164()); Optional groupId = result.getGroupId(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java index c603c652ad..506bb2472a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,7 +63,7 @@ public GroupsV2AuthorizationString getGroupsV2AuthorizationString(ServiceId aci, throws VerificationFailedException { ClientZkAuthOperations authOperations = groupsOperations.getAuthOperations(); - AuthCredentialWithPni authCredentialWithPni = authOperations.receiveAuthCredentialWithPni(aci.uuid(), pni.uuid(), redemptionTimeSeconds, authCredentialWithPniResponse); + AuthCredentialWithPni authCredentialWithPni = authOperations.receiveAuthCredentialWithPni(aci.getRawUuid(), pni.getRawUuid(), redemptionTimeSeconds, authCredentialWithPniResponse); AuthCredentialPresentation authCredentialPresentation = authOperations.createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredentialWithPni); return new GroupsV2AuthorizationString(groupSecretParams, authCredentialPresentation); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java index 1516483d62..e1988b45ac 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java @@ -39,6 +39,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedString; import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.EnabledState; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -989,7 +990,7 @@ public List decryptAddMembers(List getGroupId() { return groupId; } - public String getDestinationUuid() { + public String getDestinationServiceId() { return destinationUuid; } @@ -869,9 +870,9 @@ private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMe List readMessages = new LinkedList<>(); for (SignalServiceProtos.SyncMessage.Read read : content.getReadList()) { - ServiceId serviceId = ServiceId.parseOrNull(read.getSenderAci()); - if (serviceId != null) { - readMessages.add(new ReadMessage(serviceId, read.getTimestamp())); + ACI aci = ACI.parseOrNull(read.getSenderAci()); + if (aci != null) { + readMessages.add(new ReadMessage(aci, read.getTimestamp())); } else { Log.w(TAG, "Encountered an invalid ReadMessage! Ignoring."); } @@ -884,9 +885,9 @@ private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMe List viewedMessages = new LinkedList<>(); for (SignalServiceProtos.SyncMessage.Viewed viewed : content.getViewedList()) { - ServiceId serviceId = ServiceId.parseOrNull(viewed.getSenderAci()); - if (serviceId != null) { - viewedMessages.add(new ViewedMessage(serviceId, viewed.getTimestamp())); + ACI aci = ACI.parseOrNull(viewed.getSenderAci()); + if (aci != null) { + viewedMessages.add(new ViewedMessage(aci, viewed.getTimestamp())); } else { Log.w(TAG, "Encountered an invalid ReadMessage! Ignoring."); } @@ -896,9 +897,9 @@ private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMe } if (content.hasViewOnceOpen()) { - ServiceId serviceId = ServiceId.parseOrNull(content.getViewOnceOpen().getSenderAci()); - if (serviceId != null) { - ViewOnceOpenMessage timerRead = new ViewOnceOpenMessage(serviceId, content.getViewOnceOpen().getTimestamp()); + ACI aci = ACI.parseOrNull(content.getViewOnceOpen().getSenderAci()); + if (aci != null) { + ViewOnceOpenMessage timerRead = new ViewOnceOpenMessage(aci, content.getViewOnceOpen().getTimestamp()); return SignalServiceSyncMessage.forViewOnceOpen(timerRead); } else { throw new InvalidMessageStructureException("ViewOnceOpen message has no sender!"); @@ -1021,9 +1022,9 @@ private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMe if (content.getMessageRequestResponse().hasGroupId()) { responseMessage = MessageRequestResponseMessage.forGroup(content.getMessageRequestResponse().getGroupId().toByteArray(), type); } else { - ServiceId serviceId = ServiceId.parseOrNull(content.getMessageRequestResponse().getThreadAci()); - if (serviceId != null) { - responseMessage = MessageRequestResponseMessage.forIndividual(serviceId, type); + ACI aci = ACI.parseOrNull(content.getMessageRequestResponse().getThreadAci()); + if (aci != null) { + responseMessage = MessageRequestResponseMessage.forIndividual(aci, type); } else { throw new InvalidMessageStructureException("Message request response has an invalid thread identifier!"); } @@ -1194,7 +1195,7 @@ private static SignalServiceEditMessage createEditMessage(SignalServiceMetadata attachment.hasThumbnail() ? createAttachmentPointer(attachment.getThumbnail()) : null)); } - ServiceId author = ServiceId.parseOrNull(content.getQuote().getAuthorAci()); + ACI author = ACI.parseOrNull(content.getQuote().getAuthorAci()); if (author != null) { return new SignalServiceDataMessage.Quote(content.getQuote().getId(), author, @@ -1305,17 +1306,17 @@ private static SignalServicePreview createPreview(SignalServiceProtos.Preview pr return null; } - SignalServiceProtos.DataMessage.Reaction reaction = content.getReaction(); - ServiceId serviceId = ServiceId.parseOrNull(reaction.getTargetAuthorAci()); + SignalServiceProtos.DataMessage.Reaction reaction = content.getReaction(); + ACI aci = ACI.parseOrNull(reaction.getTargetAuthorAci()); - if (serviceId == null) { + if (aci == null) { Log.w(TAG, "Cannot parse author UUID on reaction"); return null; } return new SignalServiceDataMessage.Reaction(reaction.getEmoji(), reaction.getRemove(), - serviceId, + aci, reaction.getTargetSentTimestamp()); } @@ -1361,13 +1362,13 @@ private static SignalServicePreview createPreview(SignalServiceProtos.Preview pr return null; } - ServiceId serviceId = ServiceId.parseOrNull(content.getStoryContext().getAuthorAci()); + ACI aci = ACI.parseOrNull(content.getStoryContext().getAuthorAci()); - if (serviceId == null) { + if (aci == null) { throw new InvalidMessageStructureException("Invalid author ACI!"); } - return new SignalServiceDataMessage.StoryContext(serviceId, content.getStoryContext().getSentTimestamp()); + return new SignalServiceDataMessage.StoryContext(aci, content.getStoryContext().getSentTimestamp()); } private static @Nullable SignalServiceDataMessage.GiftBadge createGiftBadge(SignalServiceProtos.DataMessage content) throws InvalidMessageStructureException { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java index 590ae6a8e8..e011a7cda2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java @@ -169,13 +169,6 @@ public int getSourceDevice() { return envelope.getSourceDevice(); } - /** - * @return The envelope's sender as a SignalServiceAddress. - */ - public SignalServiceAddress getSourceAddress() { - return new SignalServiceAddress(ServiceId.parseOrNull(envelope.getSourceServiceId())); - } - /** * @return The envelope content type. */ @@ -286,7 +279,7 @@ private SignalServiceEnvelopeProto.Builder serializeToProto() { .setStory(isStory()); if (getSourceServiceId().isPresent()) { - builder.setSourceUuid(getSourceServiceId().get()); + builder.setSourceServiceId(getSourceServiceId().get()); } if (hasContent()) { @@ -298,7 +291,7 @@ private SignalServiceEnvelopeProto.Builder serializeToProto() { } if (hasDestinationUuid()) { - builder.setDestinationUuid(getDestinationServiceId()); + builder.setDestinationServiceId(getDestinationServiceId()); } if (hasReportingToken()) { @@ -322,7 +315,7 @@ public static SignalServiceEnvelope deserialize(byte[] serialized) { Preconditions.checkNotNull(proto); - ServiceId sourceServiceId = proto.hasSourceUuid() ? ServiceId.parseOrNull(proto.getSourceUuid()) : null; + ServiceId sourceServiceId = proto.hasSourceServiceId() ? ServiceId.parseOrNull(proto.getSourceServiceId()) : null; return new SignalServiceEnvelope(proto.getType(), sourceServiceId != null ? Optional.of(new SignalServiceAddress(sourceServiceId)) : Optional.empty(), @@ -332,7 +325,7 @@ public static SignalServiceEnvelope deserialize(byte[] serialized) { proto.getServerReceivedTimestamp(), proto.getServerDeliveredTimestamp(), proto.getServerGuid(), - proto.getDestinationUuid(), + proto.getDestinationServiceId(), proto.getUrgent(), proto.getStory(), proto.hasReportingToken() ? proto.getReportingToken().toByteArray() : null); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServicePniSignatureMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServicePniSignatureMessage.java index c53fd13d92..c2aeb67358 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServicePniSignatureMessage.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServicePniSignatureMessage.java @@ -1,7 +1,7 @@ package org.whispersystems.signalservice.api.messages; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; /** * When someone sends a message to your PNI, you need to attach one of these PNI signature messages, diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java deleted file mode 100644 index f8f9dc0bbc..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.whispersystems.signalservice.api.push; - -import com.google.protobuf.ByteString; - -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.util.UUID; - -/** - * An ACI is an "Account Identity". They're just UUIDs, but given multiple different things could be UUIDs, this wrapper exists to give us type safety around - * this *specific type* of UUID. - */ -public final class ACI extends ServiceId { - - public static ACI from(UUID uuid) { - return new ACI(uuid); - } - - public static ACI from(ServiceId serviceId) { - return new ACI(serviceId.uuid()); - } - - public static ACI fromNullable(ServiceId serviceId) { - return serviceId != null ? new ACI(serviceId.uuid()) : null; - } - - public static ACI parseOrThrow(String raw) { - return from(UUID.fromString(raw)); - } - - public static ACI parseOrNull(String raw) { - UUID uuid = UuidUtil.parseOrNull(raw); - return uuid != null ? from(uuid) : null; - } - - private ACI(UUID uuid) { - super(uuid); - } - - public ByteString toByteString() { - return UuidUtil.toByteString(uuid); - } - - public byte[] toByteArray() { - return UuidUtil.toByteArray(uuid); - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java deleted file mode 100644 index 407791b033..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.whispersystems.signalservice.api.push; - -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.util.UUID; - -/** - * A PNI is a "Phone Number Identity". They're just UUIDs, but given multiple different things could be UUIDs, this wrapper exists to give us type safety around - * this *specific type* of UUID. - */ -public final class PNI extends ServiceId { - - public static PNI from(UUID uuid) { - return new PNI(uuid); - } - - public static PNI parseOrNull(String raw) { - UUID uuid = UuidUtil.parseOrNull(raw); - return uuid != null ? from(uuid) : null; - } - - public static PNI parseOrThrow(String raw) { - return from(UUID.fromString(raw)); - } - - public static PNI parseOrThrow(byte[] raw) { - return from(UuidUtil.parseOrThrow(raw)); - } - - public static PNI parseOrNull(byte[] raw) { - UUID uuid = UuidUtil.parseOrNull(raw); - return uuid != null ? from(uuid) : null; - } - - private PNI(UUID uuid) { - super(uuid); - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.java deleted file mode 100644 index 946fe2265c..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.whispersystems.signalservice.api.push; - -import com.google.protobuf.ByteString; - -import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -import javax.annotation.Nullable; - -/** - * A wrapper around a UUID that represents an identifier for an account. Today, that is either an {@link ACI} or a {@link PNI}. - * However, that doesn't mean every {@link ServiceId} is an instance of one of those classes. In reality, we often - * do not know which we have. And it shouldn't really matter. - * - * The only times you truly know, and the only times you should actually care, is during CDS refreshes or specific inbound messages - * that link them together. - */ -public class ServiceId { - - public static final ServiceId UNKNOWN = ServiceId.from(UuidUtil.UNKNOWN_UUID); - - protected final UUID uuid; - - protected ServiceId(UUID uuid) { - this.uuid = uuid; - } - - public static ServiceId from(UUID uuid) { - return new ServiceId(uuid); - } - - public static ServiceId parseOrThrow(String raw) { - return from(UUID.fromString(raw)); - } - - public static ServiceId parseOrThrow(byte[] raw) { - return from(UuidUtil.parseOrThrow(raw)); - } - - public static @Nullable ServiceId parseOrNull(String raw) { - UUID uuid = UuidUtil.parseOrNull(raw); - return uuid != null ? from(uuid) : null; - } - - public static ServiceId parseOrNull(byte[] raw) { - UUID uuid = UuidUtil.parseOrNull(raw); - return uuid != null ? from(uuid) : null; - } - - public static ServiceId parseOrUnknown(String raw) { - ServiceId aci = parseOrNull(raw); - return aci != null ? aci : UNKNOWN; - } - - public static ServiceId fromByteString(ByteString bytes) { - return parseOrThrow(bytes.toByteArray()); - } - - public static ServiceId fromByteStringOrNull(ByteString bytes) { - UUID uuid = UuidUtil.fromByteStringOrNull(bytes); - return uuid != null ? from(uuid) : null; - } - - public static ServiceId fromByteStringOrUnknown(ByteString bytes) { - ServiceId uuid = fromByteStringOrNull(bytes); - return uuid != null ? uuid : UNKNOWN; - } - - public UUID uuid() { - return uuid; - } - - public boolean isUnknown() { - return uuid.equals(UNKNOWN.uuid); - } - - public boolean isValid() { - return !isUnknown(); - } - - public SignalProtocolAddress toProtocolAddress(int deviceId) { - return new SignalProtocolAddress(uuid.toString(), deviceId); - } - - public ByteString toByteString() { - return UuidUtil.toByteString(uuid); - } - - public byte[] toByteArray() { - return UuidUtil.toByteArray(uuid); - } - - public static List filterKnown(Collection serviceIds) { - return serviceIds.stream().filter(sid -> !sid.equals(UNKNOWN)).collect(Collectors.toList()); - } - - @Override - public String toString() { - return uuid.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ServiceId)) return false; - final ServiceId serviceId = (ServiceId) o; - return Objects.equals(uuid, serviceId.uuid); - } - - @Override - public int hashCode() { - return uuid.hashCode(); - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt new file mode 100644 index 0000000000..fc0a433f38 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt @@ -0,0 +1,181 @@ +package org.whispersystems.signalservice.api.push + +import com.google.protobuf.ByteString +import org.signal.libsignal.protocol.ServiceId.InvalidServiceIdException +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.whispersystems.signalservice.api.util.UuidUtil +import java.lang.IllegalArgumentException +import java.util.UUID +import kotlin.jvm.Throws +import org.signal.libsignal.protocol.ServiceId as LibSignalServiceId +import org.signal.libsignal.protocol.ServiceId.Aci as LibSignalAci +import org.signal.libsignal.protocol.ServiceId.Pni as LibSignalPni + +/** + * A wrapper around a UUID that represents an identifier for an account. Today, that is either an [ACI] or a [PNI]. + * However, that doesn't mean every [ServiceId] is an *instance* of one of those classes. In reality, we often + * do not know which we have. And it shouldn't really matter. + * + * The only times you truly know, and the only times you should actually care, is during CDS refreshes or specific inbound messages + * that link them together. + */ +sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServiceId) { + companion object { + /** Parses a ServiceId serialized as a string. Returns null if the ServiceId is invalid. */ + @JvmStatic + fun parseOrNull(raw: String?): ServiceId? { + if (raw == null) { + return null + } + + return try { + when (val serviceId = LibSignalServiceId.parseFromString(raw)) { + is LibSignalAci -> ACI(serviceId) + is LibSignalPni -> PNI(serviceId) + else -> null + } + } catch (e: IllegalArgumentException) { + null + } catch (e: InvalidServiceIdException) { + null + } + } + + /** Parses a ServiceId serialized as a byte array. Returns null if the ServiceId is invalid. */ + @JvmStatic + fun parseOrNull(raw: ByteArray?): ServiceId? { + if (raw == null) { + return null + } + + return try { + return when (val serviceId = LibSignalServiceId.parseFromBinary(raw)) { + is LibSignalAci -> ACI.from(serviceId.rawUUID) + is LibSignalPni -> PNI.from(serviceId.rawUUID) + else -> null + } + } catch (e: IllegalArgumentException) { + null + } catch (e: InvalidServiceIdException) { + null + } + } + + /** Parses a ServiceId serialized as a string. Crashes if the ServiceId is invalid. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!") + + /** Parses a ServiceId serialized as a byte array. Crashes if the ServiceId is invalid. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: ByteArray): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!") + + /** Parses a ServiceId serialized as a ByteString. Crashes if the ServiceId is invalid. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(bytes: ByteString): ServiceId = parseOrThrow(bytes.toByteArray()) + } + + val rawUuid: UUID = libsignalServiceId.rawUUID + + val isUnknown: Boolean = rawUuid == UuidUtil.UNKNOWN_UUID + + val isValid: Boolean = !isUnknown + + fun toProtocolAddress(deviceId: Int): SignalProtocolAddress = SignalProtocolAddress(libsignalServiceId.toServiceIdString(), deviceId) + + fun toByteString(): ByteString = ByteString.copyFrom(libsignalServiceId.toServiceIdBinary()) + + fun toByteArray(): ByteArray = libsignalServiceId.toServiceIdBinary() + + fun logString(): String = libsignalServiceId.toLogString() + + /** + * A serialized string that can be parsed via [parseOrThrow], for instance. + * Basically ACI's are just normal UUIDs, and PNI's are UUIDs with a `PNI:` prefix. + */ + override fun toString(): String = libsignalServiceId.toServiceIdString() + + data class ACI(val libsignalAci: LibSignalAci) : ServiceId(libsignalAci) { + companion object { + @JvmField + val UNKNOWN = from(UuidUtil.UNKNOWN_UUID) + + @JvmStatic + fun from(uuid: UUID): ACI = ACI(LibSignalAci(uuid)) + + @JvmStatic + fun parseOrNull(raw: String?): ACI? = ServiceId.parseOrNull(raw).let { if (it is ACI) it else null } + + @JvmStatic + fun parseOrNull(raw: ByteArray?): ACI? = ServiceId.parseOrNull(raw).let { if (it is ACI) it else null } + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String?): ACI = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ACI!") + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: ByteArray?): ACI = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ACI!") + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(bytes: ByteString): ACI = parseOrThrow(bytes.toByteArray()) + + @JvmStatic + fun parseOrUnknown(bytes: ByteString?): ACI = UuidUtil.fromByteStringOrNull(bytes)?.let { from(it) } ?: UNKNOWN + + @JvmStatic + fun parseOrUnknown(raw: String?): ACI = parseOrNull(raw) ?: UNKNOWN + } + + override fun toString(): String = super.toString() + } + + data class PNI(private val libsignalPni: LibSignalPni) : ServiceId(libsignalPni) { + companion object { + @JvmField + var UNKNOWN = from(UuidUtil.UNKNOWN_UUID) + + @JvmStatic + fun from(uuid: UUID): PNI = PNI(LibSignalPni(uuid)) + + @JvmStatic + fun parseOrNull(raw: String?): PNI? = ServiceId.parseOrNull(raw).let { if (it is PNI) it else null } + + /** Parses a plain UUID (without the `PNI:` prefix) as a PNI. Be certain that whatever you pass to this is for sure a PNI! */ + @JvmStatic + fun parseUnPrefixedOrNull(raw: String?): PNI? { + val uuid = UuidUtil.parseOrNull(raw) + return if (uuid != null) { + PNI(LibSignalPni(uuid)) + } else { + null + } + } + + @JvmStatic + fun parseOrNull(raw: ByteArray?): PNI? = ServiceId.parseOrNull(raw).let { if (it is PNI) it else null } + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String?): PNI = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid PNI!") + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: ByteArray?): PNI = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid PNI!") + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(bytes: ByteString): PNI = parseOrThrow(bytes.toByteArray()) + + /** Parses a plain UUID (without the `PNI:` prefix) as a PNI. Be certain that whatever you pass to this is for sure a PNI! */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseUnPrefixedOrThrow(raw: String?): PNI = parseUnPrefixedOrNull(raw) ?: throw IllegalArgumentException("Invalid PNI!") + } + + override fun toString(): String = super.toString() + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceIds.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceIds.java index 22d2676575..a905564255 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceIds.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ServiceIds.java @@ -2,7 +2,8 @@ import com.google.protobuf.ByteString; -import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import java.util.Objects; import java.util.UUID; @@ -37,7 +38,7 @@ public PNI requirePni() { } public boolean matches(UUID uuid) { - return uuid.equals(aci.uuid()) || (pni != null && uuid.equals(pni.uuid())); + return uuid.equals(aci.getRawUuid()) || (pni != null && uuid.equals(pni.getRawUuid())); } public boolean matches(ByteString uuid) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java index 0f8c7acee3..1e9f22d10d 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java @@ -7,8 +7,8 @@ import org.signal.libsignal.protocol.util.ByteUtil; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -130,7 +130,7 @@ private static ByteString toByteString(Map serviceIds) { for (Map.Entry entry : serviceIds.entrySet()) { try { - os.write(UuidUtil.toByteArray(entry.getKey().uuid())); + os.write(UuidUtil.toByteArray(entry.getKey().getRawUuid())); os.write(UnidentifiedAccess.deriveAccessKeyFrom(entry.getValue())); } catch (IOException e) { throw new AssertionError("Failed to write long to ByteString", e); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index 6ab6fcc30f..550d4cc00e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -81,11 +81,11 @@ public Single> getProfile(@Nonnull SignalS .setVerb("GET"); if (profileKey.isPresent()) { - ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(serviceId.uuid()); + ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(serviceId.getRawUuid()); String version = profileKeyIdentifier.serialize(); if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { - requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, serviceId.uuid(), profileKey.get()); + requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, serviceId.getRawUuid(), profileKey.get()); ProfileKeyCredentialRequest request = requestContext.getRequest(); String credentialRequest = Hex.toStringCondensed(request.serialize()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java index c4cb2f7c03..84332a360e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java @@ -382,11 +382,11 @@ private static PinnedConversation forEmpty() { static PinnedConversation fromRemote(AccountRecord.PinnedConversation remote) { if (remote.hasContact()) { - ServiceId serviceId = ServiceId.parseOrNull(remote.getContact().getUuid()); + ServiceId serviceId = ServiceId.parseOrNull(remote.getContact().getServiceId()); if (serviceId != null) { return forContact(new SignalServiceAddress(serviceId, remote.getContact().getE164())); } else { - Log.w(TAG, "Bad serviceId on pinned contact! Length: " + remote.getContact().getUuid()); + Log.w(TAG, "Bad serviceId on pinned contact! Length: " + remote.getContact().getServiceId()); return PinnedConversation.forEmpty(); } } else if (!remote.getLegacyGroupId().isEmpty()) { @@ -418,7 +418,7 @@ private AccountRecord.PinnedConversation toRemote() { if (contact.isPresent()) { AccountRecord.PinnedConversation.Contact.Builder contactBuilder = AccountRecord.PinnedConversation.Contact.newBuilder(); - contactBuilder.setUuid(contact.get().getServiceId().toString()); + contactBuilder.setServiceId(contact.get().getServiceId().toString()); if (contact.get().getNumber().isPresent()) { contactBuilder.setE164(contact.get().getNumber().get()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java index 1e9e0c0da3..ebeb5bed7a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java @@ -4,7 +4,8 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.signal.libsignal.protocol.logging.Log; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.OptionalUtil; import org.whispersystems.signalservice.api.util.ProtoUtil; @@ -25,7 +26,7 @@ public final class SignalContactRecord implements SignalRecord { private final ContactRecord proto; private final boolean hasUnknownFields; - private final ServiceId serviceId; + private final ACI aci; private final Optional pni; private final Optional e164; private final Optional profileGivenName; @@ -38,13 +39,12 @@ public final class SignalContactRecord implements SignalRecord { private final Optional identityKey; public SignalContactRecord(StorageId id, ContactRecord proto) { - this.id = id; - this.proto = proto; - this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto); - - this.serviceId = ServiceId.parseOrUnknown(proto.getServiceId()); - this.pni = OptionalUtil.absentIfEmpty(proto.getServicePni()).map(PNI::parseOrNull); - this.e164 = OptionalUtil.absentIfEmpty(proto.getServiceE164()); + this.id = id; + this.proto = proto; + this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto); + this.aci = ACI.parseOrUnknown(proto.getAci()); + this.pni = OptionalUtil.absentIfEmpty(proto.getPni()).map(PNI::parseOrNull); + this.e164 = OptionalUtil.absentIfEmpty(proto.getE164()); this.profileGivenName = OptionalUtil.absentIfEmpty(proto.getGivenName()); this.profileFamilyName = OptionalUtil.absentIfEmpty(proto.getFamilyName()); this.systemGivenName = OptionalUtil.absentIfEmpty(proto.getSystemGivenName()); @@ -75,8 +75,8 @@ public String describeDiff(SignalRecord other) { diff.add("ID"); } - if (!Objects.equals(this.getServiceId(), that.getServiceId())) { - diff.add("ServiceId"); + if (!Objects.equals(this.getAci(), that.getAci())) { + diff.add("ACI"); } if (!Objects.equals(this.getPni(), that.getPni())) { @@ -173,8 +173,8 @@ public byte[] serializeUnknownFields() { return hasUnknownFields ? proto.toByteArray() : null; } - public ServiceId getServiceId() { - return serviceId; + public ACI getAci() { + return aci; } public Optional getPni() { @@ -257,7 +257,7 @@ public boolean isHidden() { * Returns the same record, but stripped of the PNI field. Only used while PNP is in development. */ public SignalContactRecord withoutPni() { - return new SignalContactRecord(id, proto.toBuilder().clearServicePni().build()); + return new SignalContactRecord(id, proto.toBuilder().clearPni().build()); } public ContactRecord toProto() { @@ -282,7 +282,7 @@ public static final class Builder { private final StorageId id; private final ContactRecord.Builder builder; - public Builder(byte[] rawId, ServiceId serviceId, byte[] serializedUnknowns) { + public Builder(byte[] rawId, ACI aci, byte[] serializedUnknowns) { this.id = StorageId.forContact(rawId); if (serializedUnknowns != null) { @@ -291,16 +291,16 @@ public Builder(byte[] rawId, ServiceId serviceId, byte[] serializedUnknowns) { this.builder = ContactRecord.newBuilder(); } - builder.setServiceId(serviceId.toString()); + builder.setAci(aci.toString()); } public Builder setE164(String e164) { - builder.setServiceE164(e164 == null ? "" : e164); + builder.setE164(e164 == null ? "" : e164); return this; } public Builder setPni(PNI pni) { - builder.setServicePni(pni == null ? "" : pni.toString()); + builder.setPni(pni == null ? "" : pni.toString()); return this; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStoryDistributionListRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStoryDistributionListRecord.java index df3e526a15..986d822e84 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStoryDistributionListRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStoryDistributionListRecord.java @@ -28,7 +28,7 @@ public SignalStoryDistributionListRecord(StorageId id, StoryDistributionListReco this.id = id; this.proto = proto; this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto); - this.recipients = proto.getRecipientUuidsList() + this.recipients = proto.getRecipientServiceIdsList() .stream() .map(ServiceId::parseOrNull) .filter(Objects::nonNull) @@ -157,10 +157,10 @@ public Builder setName(String name) { } public Builder setRecipients(List recipients) { - builder.clearRecipientUuids(); - builder.addAllRecipientUuids(recipients.stream() - .map(SignalServiceAddress::getIdentifier) - .collect(Collectors.toList())); + builder.clearRecipientServiceIds(); + builder.addAllRecipientServiceIds(recipients.stream() + .map(SignalServiceAddress::getIdentifier) + .collect(Collectors.toList())); return this; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/CredentialsProvider.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/CredentialsProvider.java index 256f4aeae3..5ffae1bef5 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/CredentialsProvider.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/CredentialsProvider.java @@ -6,8 +6,8 @@ package org.whispersystems.signalservice.api.util; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; public interface CredentialsProvider { ACI getAci(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java index 42515e7676..252dce3f39 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.signal.libsignal.protocol.IdentityKey; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.internal.util.JsonUtil; import java.util.List; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 7a4fe6622f..5a33de0359 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -55,7 +55,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java index 25110f3573..75ca6668f0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java @@ -12,6 +12,7 @@ public class SendGroupMessageResponse { private static final String TAG = SendGroupMessageResponse.class.getSimpleName(); + // Contains serialized ServiceIds @JsonProperty private String[] uuids404; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java index 4e809491d6..9478d063aa 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java @@ -24,7 +24,7 @@ import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.logging.Log; import org.whispersystems.signalservice.api.kbs.MasterKey; -import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException; import org.whispersystems.signalservice.api.util.UuidUtil; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/StaticCredentialsProvider.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/StaticCredentialsProvider.java index 4a211f9f6c..57ca633b99 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/StaticCredentialsProvider.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/StaticCredentialsProvider.java @@ -6,8 +6,8 @@ package org.whispersystems.signalservice.internal.util; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.util.CredentialsProvider; public class StaticCredentialsProvider implements CredentialsProvider { diff --git a/libsignal/service/src/main/proto/InternalSerialization.proto b/libsignal/service/src/main/proto/InternalSerialization.proto index b3f9df0f63..7138f129e7 100644 --- a/libsignal/service/src/main/proto/InternalSerialization.proto +++ b/libsignal/service/src/main/proto/InternalSerialization.proto @@ -23,7 +23,7 @@ message SignalServiceContentProto { message SignalServiceEnvelopeProto { optional int32 type = 1; - optional string sourceUuid = 2; + optional string sourceServiceId = 2; reserved /*sourceE164*/ 3; optional int32 deviceId = 4; reserved /*legacyMessage*/ 5; @@ -32,7 +32,7 @@ message SignalServiceEnvelopeProto { optional int64 serverReceivedTimestamp = 8; optional int64 serverDeliveredTimestamp = 9; optional string serverGuid = 10; - optional string destinationUuid = 11; + optional string destinationServiceId = 11; optional bool urgent = 12 [default = true]; optional bool story = 13; optional bytes reportingToken = 14; diff --git a/libsignal/service/src/main/proto/StorageService.proto b/libsignal/service/src/main/proto/StorageService.proto index 049886e3b7..3c050ae778 100644 --- a/libsignal/service/src/main/proto/StorageService.proto +++ b/libsignal/service/src/main/proto/StorageService.proto @@ -79,9 +79,9 @@ message ContactRecord { UNVERIFIED = 2; } - string serviceId = 1; - string serviceE164 = 2; - string servicePni = 15; + string aci = 1; + string e164 = 2; + string pni = 15; bytes profileKey = 3; bytes identityKey = 4; IdentityState identityState = 5; @@ -145,8 +145,8 @@ message AccountRecord { message PinnedConversation { message Contact { - string uuid = 1; - string e164 = 2; + string serviceId = 1; + string e164 = 2; } oneof identifier { @@ -192,10 +192,10 @@ message AccountRecord { } message StoryDistributionListRecord { - bytes identifier = 1; - string name = 2; - repeated string recipientUuids = 3; - uint64 deletedAtTimestamp = 4; - bool allowsReplies = 5; - bool isBlockList = 6; + bytes identifier = 1; + string name = 2; + repeated string recipientServiceIds = 3; + uint64 deletedAtTimestamp = 4; + bool allowsReplies = 5; + bool isBlockList = 6; } diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java index a16a92c0d2..b4a4a35800 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java @@ -6,6 +6,7 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.profiles.ProfileKey; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.util.Util; @@ -24,8 +25,8 @@ public class DeviceContactsInputStreamTest { public void read() throws IOException, InvalidInputException { ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); DeviceContactsOutputStream output = new DeviceContactsOutputStream(byteArrayOut); - SignalServiceAddress addressFirst = new SignalServiceAddress(ServiceId.from(UUID.randomUUID()), "+1404555555"); - SignalServiceAddress addressSecond = new SignalServiceAddress(ServiceId.from(UUID.randomUUID()), "+1444555555"); + SignalServiceAddress addressFirst = new SignalServiceAddress(ACI.from(UUID.randomUUID()), "+1404555555"); + SignalServiceAddress addressSecond = new SignalServiceAddress(ACI.from(UUID.randomUUID()), "+1444555555"); DeviceContact first = new DeviceContact( addressFirst, diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/storage/SignalContactRecordTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/storage/SignalContactRecordTest.java index 63a71c4723..23d8dc0349 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/storage/SignalContactRecordTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/storage/SignalContactRecordTest.java @@ -2,22 +2,23 @@ import org.junit.Test; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; public class SignalContactRecordTest { - private static final ServiceId SID_A = ServiceId.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); - private static final String E164_A = "+16108675309"; + private static final ACI ACI_A = ACI.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); + private static final String E164_A = "+16108675309"; @Test public void contacts_with_same_identity_key_contents_are_equal() { byte[] profileKey = new byte[32]; byte[] profileKeyCopy = profileKey.clone(); - SignalContactRecord a = contactBuilder(1, SID_A, E164_A, "a").setIdentityKey(profileKey).build(); - SignalContactRecord b = contactBuilder(1, SID_A, E164_A, "a").setIdentityKey(profileKeyCopy).build(); + SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKey).build(); + SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKeyCopy).build(); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); @@ -29,8 +30,8 @@ public void contacts_with_different_identity_key_contents_are_not_equal() { byte[] profileKeyCopy = profileKey.clone(); profileKeyCopy[0] = 1; - SignalContactRecord a = contactBuilder(1, SID_A, E164_A, "a").setIdentityKey(profileKey).build(); - SignalContactRecord b = contactBuilder(1, SID_A, E164_A, "a").setIdentityKey(profileKeyCopy).build(); + SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKey).build(); + SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKeyCopy).build(); assertNotEquals(a, b); assertNotEquals(a.hashCode(), b.hashCode()); @@ -46,7 +47,7 @@ private static byte[] byteArray(int a) { } private static SignalContactRecord.Builder contactBuilder(int key, - ServiceId serviceId, + ACI serviceId, String e164, String givenName) { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/serialize/SignalServiceAddressProtobufSerializerTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/serialize/SignalServiceAddressProtobufSerializerTest.java index 4903a180bc..4aebc44e40 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/serialize/SignalServiceAddressProtobufSerializerTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/serialize/SignalServiceAddressProtobufSerializerTest.java @@ -2,6 +2,7 @@ import org.junit.Test; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.serialize.protos.AddressProto; @@ -14,7 +15,7 @@ public final class SignalServiceAddressProtobufSerializerTest { @Test public void serialize_and_deserialize_uuid_address() { - SignalServiceAddress address = new SignalServiceAddress(ServiceId.from(UUID.randomUUID()), Optional.empty()); + SignalServiceAddress address = new SignalServiceAddress(ACI.from(UUID.randomUUID()), Optional.empty()); AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address); SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto); @@ -23,7 +24,7 @@ public void serialize_and_deserialize_uuid_address() { @Test public void serialize_and_deserialize_both_address() { - SignalServiceAddress address = new SignalServiceAddress(ServiceId.from(UUID.randomUUID()), Optional.of("+15552345678")); + SignalServiceAddress address = new SignalServiceAddress(ACI.from(UUID.randomUUID()), Optional.of("+15552345678")); AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address); SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto); diff --git a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt index 5c6df8ee78..09607c72cd 100644 --- a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt +++ b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt @@ -20,7 +20,7 @@ import org.whispersystems.signalservice.api.crypto.ContentHint import org.whispersystems.signalservice.api.crypto.EnvelopeContent import org.whispersystems.signalservice.api.crypto.SignalServiceCipher import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess -import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.push.OutgoingPushMessage import org.whispersystems.signalservice.internal.push.SignalServiceProtos @@ -41,7 +41,7 @@ class SignalClient { private val trustRoot: ECKeyPair = Curve.generateKeyPair() } - private val serviceId: ServiceId = ServiceId.from(UUID.randomUUID()) + private val aci: ACI = ACI.from(UUID.randomUUID()) private val store: SignalServiceAccountDataStore = InMemorySignalServiceAccountDataStore() @@ -60,21 +60,21 @@ class SignalClient { private val senderCertificate: SenderCertificate = createCertificateFor( trustRoot = trustRoot, - uuid = serviceId.uuid(), + uuid = aci.rawUuid, e164 = "+${Random.nextLong(1111111111L, 9999999999L)}", deviceId = 1, identityKey = store.identityKeyPair.publicKey.publicKey, expires = Long.MAX_VALUE ) - private val cipher = SignalServiceCipher(SignalServiceAddress(serviceId), 1, store, TestSessionLock(), CertificateValidator(trustRoot.publicKey)) + private val cipher = SignalServiceCipher(SignalServiceAddress(aci), 1, store, TestSessionLock(), CertificateValidator(trustRoot.publicKey)) /** * Sets up sessions using the [to] client's [preKeyBundle]. Note that you can only initialize a client once * since we currently only make a single prekey bundle. */ fun initializeSession(to: SignalClient) { - val address = SignalProtocolAddress(to.serviceId.toString(), 1) + val address = SignalProtocolAddress(to.aci.toString(), 1) SessionBuilder(store, address).process(to.preKeyBundle) } @@ -91,7 +91,7 @@ class SignalClient { .build() val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt( - SignalProtocolAddress(to.serviceId.toString(), 1), + SignalProtocolAddress(to.aci.toString(), 1), Optional.empty(), EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty()) ) @@ -99,9 +99,9 @@ class SignalClient { val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content) return SignalServiceProtos.Envelope.newBuilder() - .setSourceServiceId(serviceId.toString()) + .setSourceServiceId(aci.toString()) .setSourceDevice(1) - .setDestinationServiceId(to.serviceId.toString()) + .setDestinationServiceId(to.aci.toString()) .setTimestamp(sentTimestamp) .setServerTimestamp(sentTimestamp) .setServerGuid(UUID.randomUUID().toString()) @@ -124,7 +124,7 @@ class SignalClient { .build() val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt( - SignalProtocolAddress(to.serviceId.toString(), 1), + SignalProtocolAddress(to.aci.toString(), 1), Optional.of(UnidentifiedAccess(to.unidentifiedAccessKey, senderCertificate.serialized, false)), EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty()) ) @@ -132,9 +132,9 @@ class SignalClient { val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content) return SignalServiceProtos.Envelope.newBuilder() - .setSourceServiceId(serviceId.toString()) + .setSourceServiceId(aci.toString()) .setSourceDevice(1) - .setDestinationServiceId(to.serviceId.toString()) + .setDestinationServiceId(to.aci.toString()) .setTimestamp(sentTimestamp) .setServerTimestamp(sentTimestamp) .setServerGuid(UUID.randomUUID().toString()) From c012ead143941b98a3559db65643b67e2950ac85 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 28 Jul 2023 13:05:21 -0400 Subject: [PATCH 11/66] Validate ServiceIds on envelopes. --- .../securesms/messages/MessageDecryptor.kt | 4 +- .../api/messages/EnvelopeContentValidator.kt | 85 ++++++++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index f694922f9c..9a4c08cce2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -85,8 +85,8 @@ object MessageDecryptor { envelope: Envelope, serverDeliveredTimestamp: Long ): Result { - val selfAci: ServiceId = SignalStore.account().requireAci() - val selfPni: ServiceId = SignalStore.account().requirePni() + val selfAci: ACI = SignalStore.account().requireAci() + val selfPni: PNI = SignalStore.account().requirePni() val destination: ServiceId = envelope.getDestination(selfAci, selfPni) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt index d67a662e08..3726940336 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -5,7 +5,8 @@ import org.signal.libsignal.zkgroup.InvalidInputException import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation import org.whispersystems.signalservice.api.InvalidMessageStructureException -import org.whispersystems.signalservice.api.util.UuidUtil +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content @@ -34,6 +35,12 @@ object EnvelopeContentValidator { } } + if (envelope.hasSourceServiceId() && envelope.sourceServiceId.isInvalidServiceId()) { + return Result.Invalid("Envelope had an invalid sourceServiceId!") + } + + // Reminder: envelope.destinationServiceId was already validated since we need that for decryption + return when { envelope.story && !content.meetsStoryFlagCriteria() -> Result.Invalid("Envelope was flagged as a story, but it did not have any story-related content!") content.hasDataMessage() -> validateDataMessage(envelope, content.dataMessage) @@ -67,8 +74,12 @@ object EnvelopeContentValidator { Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}") } - if (dataMessage.hasQuote() && dataMessage.quote.authorAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[DataMessage] Invalid UUID on quote!") + if (dataMessage.hasQuote() && dataMessage.quote.authorAci.isNullOrInvalidAci()) { + return Result.Invalid("[DataMessage] Invalid ACI on quote!") + } + + if (dataMessage.contactList.any { it.hasAvatar() && it.avatar.avatar.isPresentAndInvalid() }) { + return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!") } if (dataMessage.contactList.any { it.hasAvatar() && it.avatar.avatar.isPresentAndInvalid() }) { @@ -79,8 +90,8 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!") } - if (dataMessage.bodyRangesList.any { it.hasMentionAci() && it.mentionAci.isNullOrInvalidOrUnknownUuid() }) { - return Result.Invalid("[DataMessage] Invalid UUID on body range!") + if (dataMessage.bodyRangesList.any { it.hasMentionAci() && it.mentionAci.isNullOrInvalidAci() }) { + return Result.Invalid("[DataMessage] Invalid ACI on body range!") } if (dataMessage.hasSticker() && dataMessage.sticker.data.isNullOrInvalid()) { @@ -91,8 +102,8 @@ object EnvelopeContentValidator { if (!dataMessage.reaction.hasTargetSentTimestamp()) { return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!") } - if (dataMessage.reaction.targetAuthorAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.reaction!") + if (dataMessage.reaction.targetAuthorAci.isNullOrInvalidAci()) { + return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.reaction!") } } @@ -100,8 +111,8 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!") } - if (dataMessage.hasStoryContext() && dataMessage.storyContext.authorAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.storyContext!") + if (dataMessage.hasStoryContext() && dataMessage.storyContext.authorAci.isNullOrInvalidAci()) { + return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.storyContext!") } if (dataMessage.hasGiftBadge()) { @@ -130,7 +141,7 @@ object EnvelopeContentValidator { private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage): Result { if (syncMessage.hasSent()) { - val validAddress = syncMessage.sent.destinationServiceId.isValidUuid() + val validAddress = syncMessage.sent.destinationServiceId.isValidServiceId() val hasDataGroup = syncMessage.sent.message?.hasGroupV2() ?: false val hasStoryGroup = syncMessage.sent.storyMessage?.hasGroup() ?: false val hasStoryManifest = syncMessage.sent.storyMessageRecipientsList.isNotEmpty() @@ -153,8 +164,8 @@ object EnvelopeContentValidator { } for (status in syncMessage.sent.unidentifiedStatusList) { - if (status.destinationServiceId.isNullOrInvalidUuid()) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.sent.unidentifiedStatusList!") + if (status.destinationServiceId.isNullOrInvalidServiceId()) { + return Result.Invalid("[SyncMessage] Invalid ServiceId in SyncMessage.sent.unidentifiedStatusList!") } } @@ -171,32 +182,36 @@ object EnvelopeContentValidator { } } - if (syncMessage.readList.any { it.senderAci.isNullOrInvalidOrUnknownUuid() }) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.readList!") + if (syncMessage.readList.any { it.senderAci.isNullOrInvalidAci() }) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.readList!") } - if (syncMessage.viewedList.any { it.senderAci.isNullOrInvalidOrUnknownUuid() }) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewList!") + if (syncMessage.viewedList.any { it.senderAci.isNullOrInvalidAci() }) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewList!") } - if (syncMessage.hasViewOnceOpen() && syncMessage.viewOnceOpen.senderAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewOnceOpen!") + if (syncMessage.hasViewOnceOpen() && syncMessage.viewOnceOpen.senderAci.isNullOrInvalidAci()) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewOnceOpen!") } - if (syncMessage.hasVerified() && syncMessage.verified.destinationAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.verified!") + if (syncMessage.hasVerified() && syncMessage.verified.destinationAci.isNullOrInvalidAci()) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.verified!") } if (syncMessage.stickerPackOperationList.any { !it.hasPackId() }) { return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!") } - if (syncMessage.hasBlocked() && syncMessage.blocked.acisList.any { it.isNullOrInvalidOrUnknownUuid() }) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.blocked!") + if (syncMessage.hasBlocked() && syncMessage.blocked.acisList.any { it.isNullOrInvalidAci() }) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.blocked!") + } + + if (syncMessage.hasMessageRequestResponse() && !syncMessage.messageRequestResponse.hasGroupId() && syncMessage.messageRequestResponse.threadAci.isNullOrInvalidAci()) { + return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.messageRequestResponse!") } - if (syncMessage.hasMessageRequestResponse() && !syncMessage.messageRequestResponse.hasGroupId() && syncMessage.messageRequestResponse.threadAci.isNullOrInvalidOrUnknownUuid()) { - return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.messageRequestResponse!") + if (syncMessage.hasOutgoingPayment() && syncMessage.outgoingPayment.recipientServiceId.isNullOrInvalidServiceId()) { + return Result.Invalid("[SyncMessage] Invalid ServiceId in SyncMessage.outgoingPayment!") } return Result.Valid @@ -261,7 +276,7 @@ object EnvelopeContentValidator { return Result.Invalid("[EditMessage] Invalid AttachmentPointer on DataMessage.previewList.image!") } - if (dataMessage.bodyRangesList.any { it.hasMentionAci() && it.mentionAci.isNullOrInvalidOrUnknownUuid() }) { + if (dataMessage.bodyRangesList.any { it.hasMentionAci() && it.mentionAci.isNullOrInvalidAci() }) { return Result.Invalid("[EditMessage] Invalid UUID on body range!") } @@ -284,16 +299,24 @@ object EnvelopeContentValidator { return this != null && this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET } - private fun String?.isValidUuid(): Boolean { - return UuidUtil.isUuid(this) + private fun String?.isValidServiceId(): Boolean { + val parsed = ServiceId.parseOrNull(this) + return parsed != null && parsed.isValid + } + + private fun String?.isNullOrInvalidServiceId(): Boolean { + val parsed = ServiceId.parseOrNull(this) + return parsed == null || parsed.isUnknown } - private fun String?.isNullOrInvalidOrUnknownUuid(): Boolean { - return !UuidUtil.isUuid(this) || this == UuidUtil.UNKNOWN_UUID_STRING + private fun String.isInvalidServiceId(): Boolean { + val parsed = ServiceId.parseOrNull(this) + return parsed == null || parsed.isUnknown } - private fun String?.isNullOrInvalidUuid(): Boolean { - return !UuidUtil.isUuid(this) + private fun String?.isNullOrInvalidAci(): Boolean { + val parsed = ACI.parseOrNull(this) + return parsed == null || parsed.isUnknown } private fun Content?.meetsStoryFlagCriteria(): Boolean { From 66792f2d56af70e6835430aed4dee25e98a79830 Mon Sep 17 00:00:00 2001 From: Clark Date: Fri, 28 Jul 2023 13:11:46 -0400 Subject: [PATCH 12/66] Add heuristics for delayed notifications. --- .../database/LocalMetricsDatabase.kt | 12 +- .../gcm/FcmFetchForegroundService.kt | 9 +- .../securesms/gcm/FcmFetchManager.kt | 13 +- .../securesms/gcm/FcmReceiveService.java | 2 + .../messages/IncomingMessageObserver.kt | 2 + .../SlowNotificationHeuristics.kt | 112 ++++++++++++++++++ .../securesms/util/LocalMetrics.kt | 7 +- .../securesms/util/SignalLocalMetrics.java | 46 ++++++- 8 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/SlowNotificationHeuristics.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt index 79b93b7f14..437771e594 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt @@ -153,6 +153,16 @@ class LocalMetricsDatabase private constructor( writableDatabase.delete(TABLE_NAME, null, null) } + fun getOldestMetricTime(eventName: String): Long { + readableDatabase.rawQuery("SELECT $CREATED_AT FROM $TABLE_NAME WHERE $EVENT_NAME = ? ORDER BY $CREATED_AT ASC", SqlUtil.buildArgs(eventName)).use { cursor -> + return if (cursor.moveToFirst()) { + cursor.getLong(0) + } else { + 0 + } + } + } + fun getMetrics(): List { val db = readableDatabase @@ -213,7 +223,7 @@ class LocalMetricsDatabase private constructor( } } - private fun eventPercent(eventName: String, percent: Int): Long { + fun eventPercent(eventName: String, percent: Int): Long { return percentile(EventTotals.VIEW_NAME, "$EVENT_NAME = '$eventName'", percent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchForegroundService.kt b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchForegroundService.kt index 319dfb9f17..5f3bcf3860 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchForegroundService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchForegroundService.kt @@ -53,7 +53,12 @@ class FcmFetchForegroundService : Service() { private var foregroundServiceState: State = State.STOPPED - fun startServiceIfNecessary(context: Context) { + /** + * Attempts to start the foreground service if it isn't already running. + * + * @return false if we failed to start the foreground service + */ + fun startServiceIfNecessary(context: Context): Boolean { synchronized(this) { when (foregroundServiceState) { State.STOPPING -> foregroundServiceState = State.RESTARTING @@ -64,11 +69,13 @@ class FcmFetchForegroundService : Service() { } catch (e: IllegalStateException) { Log.e(TAG, "Failed to start foreground service", e) State.STOPPED + return false } } else -> Log.i(TAG, "Already started foreground service") } } + return true } fun stopServiceIfNecessary(context: Context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt index 7129da1c44..30c4fa2676 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt @@ -51,22 +51,21 @@ object FcmFetchManager { @Volatile private var highPriority = false - /** - * @return True if a service was successfully started, otherwise false. - */ @JvmStatic fun startBackgroundService(context: Context) { Log.i(TAG, "Starting in the background.") context.startService(Intent(context, FcmFetchBackgroundService::class.java)) + SignalLocalMetrics.FcmServiceStartSuccess.onFcmStarted() } - /** - * @return True if a service was successfully started, otherwise false. - */ @JvmStatic fun startForegroundService(context: Context) { Log.i(TAG, "Starting in the foreground.") - FcmFetchForegroundService.startServiceIfNecessary(context) + if (FcmFetchForegroundService.startServiceIfNecessary(context)) { + SignalLocalMetrics.FcmServiceStartSuccess.onFcmStarted() + } else { + SignalLocalMetrics.FcmServiceStartFailure.onFcmFailedToStart() + } } private fun postMayHaveMessagesNotification(context: Context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java index 60a28d9f48..6fd49950b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.registration.PushChallengeRequest; import org.thoughtcrime.securesms.util.NetworkUtil; +import org.thoughtcrime.securesms.util.SignalLocalMetrics; import java.util.Locale; @@ -85,6 +86,7 @@ private static void handleReceivedNotification(Context context, @Nullable Remote } } catch (Exception e) { Log.w(TAG, "Failed to start service.", e); + SignalLocalMetrics.FcmServiceStartFailure.onFcmFailedToStart(); } FcmFetchManager.enqueueFetch(context, highPriority); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt index 3dbd0cc6b1..002d54b452 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -298,6 +298,8 @@ class IncomingMessageObserver(private val context: Application) { val localReceiveMetric = SignalLocalMetrics.MessageReceive.start() val result = MessageDecryptor.decrypt(context, bufferedProtocolStore, envelope, serverDeliveredTimestamp) localReceiveMetric.onEnvelopeDecrypted() + + SignalLocalMetrics.MessageLatency.onMessageReceived(envelope.serverTimestamp, serverDeliveredTimestamp) when (result) { is MessageDecryptor.Result.Success -> { val job = PushProcessMessageJobV2.processOrDefer(messageContentProcessor, result, localReceiveMetric) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SlowNotificationHeuristics.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/SlowNotificationHeuristics.kt new file mode 100644 index 0000000000..062d181575 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SlowNotificationHeuristics.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.notifications + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.LocalMetricsDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.util.SignalLocalMetrics + +/** + * Heuristic for estimating if a user has been experiencing issues with delayed notifications. + * + * This uses local metrics based off of message latency, failed service starts, and queue drain + * timeouts. + * + * This will enable us to attempt to improve notifications for users who are experiencing these issues. + */ +object SlowNotificationHeuristics { + + private val TAG = Log.tag(SlowNotificationHeuristics::class.java) + + fun isHavingDelayedNotifications(configuration: Configuration): Boolean { + val db = LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) + + val metrics = db.getMetrics() + + val failedServiceStarts = hasRepeatedFailedServiceStarts(metrics, configuration.minimumEventAgeMs, configuration.minimumServiceEventCount, configuration.serviceStartFailurePercentage) + val failedQueueDrains = isFailingToDrainQueue(metrics, configuration.minimumEventAgeMs, configuration.weeklyFailedQueueDrains) + val longMessageLatency = hasLongMessageLatency(metrics, configuration.minimumEventAgeMs, configuration.messageLatencyPercentage, configuration.minimumMessageLatencyEvents, configuration.messageLatencyThreshold) + + if (failedServiceStarts || failedQueueDrains || longMessageLatency) { + Log.w(TAG, "User seems to be having delayed notifications: failed-service-starts=$failedServiceStarts failedQueueDrains=$failedQueueDrains longMessageLatency=$longMessageLatency") + return true + } + return false + } + + private fun hasRepeatedFailedServiceStarts(metrics: List, minimumEventAgeMs: Long, minimumEventCount: Int, failurePercentage: Float): Boolean { + if (!haveEnoughData(SignalLocalMetrics.FcmServiceStartSuccess.NAME, minimumEventAgeMs) && !haveEnoughData(SignalLocalMetrics.FcmServiceStartFailure.NAME, minimumEventAgeMs)) { + Log.d(TAG, "insufficient data for service starts") + return false + } + + val successes = metrics.filter { it.name == SignalLocalMetrics.FcmServiceStartSuccess.NAME } + val failures = metrics.filter { it.name == SignalLocalMetrics.FcmServiceStartFailure.NAME } + + if ((successes.size + failures.size) < minimumEventCount) { + Log.d(TAG, "insufficient service start events") + return false + } + + if (failures.size / (failures.size + successes.size) >= failurePercentage) { + Log.w(TAG, "User often unable start FCM service. ${failures.size} failed : ${successes.size} successful") + return true + } + return false + } + + private fun isFailingToDrainQueue(metrics: List, minimumEventAgeMs: Long, failureThreshold: Int): Boolean { + if (!haveEnoughData(SignalLocalMetrics.PushWebsocketFetch.SUCCESS_EVENT, minimumEventAgeMs) && !haveEnoughData(SignalLocalMetrics.PushWebsocketFetch.TIMEOUT_EVENT, minimumEventAgeMs)) { + Log.d(TAG, "insufficient data for failed queue drains") + return false + } + val failures = metrics.filter { it.name == SignalLocalMetrics.PushWebsocketFetch.TIMEOUT_EVENT } + if (failures.size < failureThreshold) { + return false + } + Log.w(TAG, "User has repeatedly failed to drain queue ${failures.size} events") + return true + } + + private fun hasLongMessageLatency(metrics: List, minimumEventAgeMs: Long, percentage: Int, messageThreshold: Int, durationThreshold: Long): Boolean { + if (!haveEnoughData(SignalLocalMetrics.MessageLatency.NAME, minimumEventAgeMs)) { + Log.d(TAG, "insufficient data for message latency") + return false + } + val eventCount = metrics.count { it.name == SignalLocalMetrics.MessageLatency.NAME } + if (eventCount < messageThreshold) { + Log.d(TAG, "not enough messages for message latency") + return false + } + val db = LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) + val averageLatency = db.eventPercent(SignalLocalMetrics.MessageLatency.NAME, percentage.coerceAtMost(100).coerceAtLeast(0)) + + val longMessageLatency = averageLatency > durationThreshold + if (longMessageLatency) { + Log.w(TAG, "User has high average message latency of $averageLatency ms over $eventCount events") + } + return longMessageLatency + } + + private fun haveEnoughData(eventName: String, minimumEventAgeMs: Long): Boolean { + val db = LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) + + val oldestEvent = db.getOldestMetricTime(eventName) + + return !(oldestEvent == 0L || oldestEvent > System.currentTimeMillis() - minimumEventAgeMs) + } +} + +data class Configuration( + val minimumEventAgeMs: Long, + val minimumServiceEventCount: Int, + val serviceStartFailurePercentage: Float, + val weeklyFailedQueueDrains: Int, + val minimumMessageLatencyEvents: Int, + val messageLatencyThreshold: Long, + val messageLatencyPercentage: Int +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LocalMetrics.kt b/app/src/main/java/org/thoughtcrime/securesms/util/LocalMetrics.kt index edff293fef..e8aa12119e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LocalMetrics.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LocalMetrics.kt @@ -87,14 +87,15 @@ object LocalMetrics { * * If an event with the provided ID does not exist, this is effectively a no-op. */ - fun splitWithDuration(id: String, split: String, duration: Long) { - val time = System.currentTimeMillis() + @JvmOverloads + fun splitWithDuration(id: String, split: String, duration: Long, timeunit: TimeUnit = TimeUnit.MILLISECONDS) { + val time = SystemClock.elapsedRealtimeNanos() executor.execute { val lastTime: Long? = lastSplitTimeById[id] val splitDoesNotExist: Boolean = eventsById[id]?.splits?.none { it.name == split } ?: true if (lastTime != null && splitDoesNotExist) { - eventsById[id]?.splits?.add(LocalMetricsSplit(split, duration)) + eventsById[id]?.splits?.add(LocalMetricsSplit(split, TimeUnit.NANOSECONDS.convert(duration, timeunit))) lastSplitTimeById[id] = time } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java b/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java index 49f1267eec..c30a59ff93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java @@ -203,9 +203,51 @@ private static void end(long messageId) { } } + public static final class MessageLatency { + public static final String NAME = "message-latency"; + + private static final String SPLIT_LATENCY = "latency"; + + public static void onMessageReceived(long serverReceiveTimestamp, long serverDeliverTimestamp) { + long latency = serverDeliverTimestamp - serverReceiveTimestamp; + + String id = NAME + System.currentTimeMillis(); + LocalMetrics.getInstance().start(id, NAME); + LocalMetrics.getInstance().splitWithDuration(id, SPLIT_LATENCY, latency); + LocalMetrics.getInstance().end(id); + } + } + + public static final class FcmServiceStartFailure { + public static final String NAME = "fcm-service-start-failure"; + + private static final String SPLIT_EVENT = "event"; + + public static void onFcmFailedToStart() { + String id = NAME + System.currentTimeMillis(); + LocalMetrics.getInstance().start(id, NAME); + LocalMetrics.getInstance().splitWithDuration(id, SPLIT_EVENT, 1); + LocalMetrics.getInstance().end(id); + } + + } + + public static final class FcmServiceStartSuccess { + public static final String NAME = "fcm-service-start-success"; + + private static final String SPLIT_EVENT = "event"; + + public static void onFcmStarted() { + String id = NAME + System.currentTimeMillis(); + LocalMetrics.getInstance().start(id, NAME); + LocalMetrics.getInstance().splitWithDuration(id, SPLIT_EVENT, 1); + LocalMetrics.getInstance().end(id); + } + + } public static final class PushWebsocketFetch { - private static final String SUCCESS_EVENT = "push-websocket-fetch"; - private static final String TIMEOUT_EVENT = "timed-out-fetch"; + public static final String SUCCESS_EVENT = "push-websocket-fetch"; + public static final String TIMEOUT_EVENT = "timed-out-fetch"; private static final String SPLIT_BATCH_PROCESSED = "batches-processed"; private static final String SPLIT_PROCESS_TIME = "fetch-time"; From b11d653fc0f646146185f9dd6c75248347070b4a Mon Sep 17 00:00:00 2001 From: Clark Date: Fri, 28 Jul 2023 13:44:58 -0400 Subject: [PATCH 13/66] Exit edit message mode on message send. --- .../conversation/ConversationFragment.java | 9 +++++++ .../ConversationParentFragment.java | 16 ++++++++++++ .../conversation/v2/ConversationFragment.kt | 11 ++++++-- .../stories/dialogs/StoryContextMenu.kt | 2 +- .../reply/group/StoryGroupReplyFragment.kt | 2 +- .../securesms/util/DeleteDialog.kt | 25 +++++++++++-------- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index f3a7f9f3a8..7a14582a5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1085,6 +1085,10 @@ protected Void doInBackground(Void... voids) { return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + for (MessageRecord messageRecord : messageRecords) { + listener.onDeleteMessage(messageRecord.getId()); + } }); int deleteForEveryoneResId = isNoteToSelfDelete ? R.string.ConversationFragment_delete_everywhere : R.string.ConversationFragment_delete_for_everyone; @@ -1110,6 +1114,9 @@ private static boolean isNoteToSelfDelete(Set messageRecords) { private void handleDeleteForEveryone(Set messageRecords) { Runnable deleteForEveryone = () -> { + for (MessageRecord messageRecord : messageRecords) { + listener.onRemoteDeleteMessage(messageRecord.getId()); + } SignalExecutors.BOUNDED.execute(() -> { for (MessageRecord message : messageRecords) { MessageSender.sendRemoteDelete(message.getId()); @@ -1477,6 +1484,8 @@ void handleReaction(@NonNull ConversationMessage conversationMessage, void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onUnregisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onInviteToSignal(); + void onDeleteMessage(long id); + void onRemoteDeleteMessage(long targetId); boolean isInBubble(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index bad1e9d39c..4c6d892036 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -4054,6 +4054,22 @@ public void onInviteToSignal() { handleInviteLink(); } + @Override + public void onDeleteMessage(long id) { + MessageId messageId = inputPanel.getEditMessageId(); + if (messageId != null && messageId.getId() == id) { + inputPanel.exitEditMessageMode(); + } + } + + @Override + public void onRemoteDeleteMessage(long targetId) { + MessageId messageId = inputPanel.getEditMessageId(); + if (messageId != null && messageId.getId() == targetId) { + inputPanel.exitEditMessageMode(); + } + } + @Override public void onCursorChanged() { if (!reactionDelegate.isShowing()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 0072726f97..d02068d4a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -2119,10 +2119,17 @@ class ConversationFragment : } private fun handleDeleteMessages(messageParts: Set) { + val records = messageParts.map(MultiselectPart::getMessageRecord).toSet() disposables += DeleteDialog.show( context = requireContext(), - messageRecords = messageParts.map(MultiselectPart::getMessageRecord).toSet() - ).subscribe() + messageRecords = records + ).subscribe { (deleted: Boolean, _: Boolean) -> + if (!deleted) return@subscribe + val editMessageId = inputPanel.editMessageId?.id + if (editMessageId != null && records.any { it.id == editMessageId }) { + inputPanel.exitEditMessageMode() + } + } } private inner class SwipeAvailabilityProvider : ConversationItemSwipeCallback.SwipeAvailabilityProvider { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt index 29d166f317..9176875403 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt @@ -45,7 +45,7 @@ object StoryContextMenu { title = context.getString(R.string.MyStories__delete_story), message = context.getString(R.string.MyStories__this_story_will_be_deleted), forceRemoteDelete = true - ) + ).map { (_, deletedThread) -> deletedThread } } fun save(context: Context, messageRecord: MessageRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt index bfa8a208b0..dd1401044a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt @@ -312,7 +312,7 @@ class StoryGroupReplyFragment : } private fun onDeleteClick(messageRecord: MessageRecord) { - lifecycleDisposable += DeleteDialog.show(requireActivity(), setOf(messageRecord)).subscribe { didDeleteThread -> + lifecycleDisposable += DeleteDialog.show(requireActivity(), setOf(messageRecord)).subscribe { (_, didDeleteThread) -> if (didDeleteThread) { throw AssertionError("We should never end up deleting a Group Thread like this.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt index 83d353ded8..44d0731438 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt @@ -23,7 +23,8 @@ object DeleteDialog { * @param message The dialog message, or null * @param forceRemoteDelete Allow remote deletion, even if it would normally be disallowed * - * @return a Single, who's value notes whether or not a thread deletion occurred. + * @return a Single, who's value is a pair that notes whether or not a deletion attempt + * happened at all, as well as if a thread deletion occurred. */ fun show( context: Context, @@ -31,7 +32,7 @@ object DeleteDialog { title: CharSequence = context.resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageRecords.size, messageRecords.size), message: CharSequence? = null, forceRemoteDelete: Boolean = false - ): Single = Single.create { emitter -> + ): Single> = Single.create { emitter -> val builder = MaterialAlertDialogBuilder(context) builder.setTitle(title) @@ -44,7 +45,9 @@ object DeleteDialog { builder.setPositiveButton(R.string.ConversationFragment_delete_for_everyone) { _, _ -> deleteForEveryone(messageRecords, emitter) } } else { builder.setPositiveButton(if (isNoteToSelfDelete) R.string.ConversationFragment_delete_on_this_device else R.string.ConversationFragment_delete_for_me) { _, _ -> - DeleteProgressDialogAsyncTask(context, messageRecords, emitter::onSuccess).executeOnExecutor(SignalExecutors.BOUNDED) + DeleteProgressDialogAsyncTask(context, messageRecords) { + emitter.onSuccess(Pair(true, it)) + }.executeOnExecutor(SignalExecutors.BOUNDED) } if (MessageConstraintsUtil.isValidRemoteDeleteSend(messageRecords, System.currentTimeMillis()) && (!isNoteToSelfDelete || TextSecurePreferences.isMultiDevice(context))) { @@ -52,8 +55,8 @@ object DeleteDialog { } } - builder.setNegativeButton(android.R.string.cancel) { _, _ -> emitter.onSuccess(false) } - builder.setOnCancelListener { emitter.onSuccess(false) } + builder.setNegativeButton(android.R.string.cancel) { _, _ -> emitter.onSuccess(Pair(false, false)) } + builder.setOnCancelListener { emitter.onSuccess(Pair(false, false)) } builder.show() } @@ -61,8 +64,8 @@ object DeleteDialog { return messageRecords.all { messageRecord: MessageRecord -> messageRecord.isOutgoing && messageRecord.toRecipient.isSelf } } - private fun handleDeleteForEveryone(context: Context, messageRecords: Set, emitter: SingleEmitter) { - if (SignalStore.uiHints().hasConfirmedDeleteForEveryoneOnce() || isNoteToSelfDelete(messageRecords)) { + private fun handleDeleteForEveryone(context: Context, messageRecords: Set, emitter: SingleEmitter>) { + if (SignalStore.uiHints().hasConfirmedDeleteForEveryoneOnce()) { deleteForEveryone(messageRecords, emitter) } else { MaterialAlertDialogBuilder(context) @@ -71,19 +74,19 @@ object DeleteDialog { SignalStore.uiHints().markHasConfirmedDeleteForEveryoneOnce() deleteForEveryone(messageRecords, emitter) } - .setNegativeButton(android.R.string.cancel) { _, _ -> emitter.onSuccess(false) } - .setOnCancelListener { emitter.onSuccess(false) } + .setNegativeButton(android.R.string.cancel) { _, _ -> emitter.onSuccess(Pair(false, false)) } + .setOnCancelListener { emitter.onSuccess(Pair(false, false)) } .show() } } - private fun deleteForEveryone(messageRecords: Set, emitter: SingleEmitter) { + private fun deleteForEveryone(messageRecords: Set, emitter: SingleEmitter>) { SignalExecutors.BOUNDED.execute { messageRecords.forEach { message -> MessageSender.sendRemoteDelete(message.id) } - emitter.onSuccess(false) + emitter.onSuccess(Pair(true, false)) } } From a2c3b5d64ef9277f511a03f8cd903505f83e6733 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 31 Jul 2023 10:28:18 -0700 Subject: [PATCH 14/66] Adopt libsignal 0.30.0 and ServiceIds for group members. Co-authored-by: Greyson Parrelli --- .../ConversationSettingsRepository.kt | 4 +- .../conversation/ConversationUpdateItem.java | 15 +- .../securesms/database/GroupTable.kt | 54 +++-- .../securesms/database/MessageTable.kt | 4 +- .../securesms/database/model/GroupRecord.kt | 2 +- .../model/GroupsV2UpdateMessageProducer.java | 55 ++--- .../database/model/MessageRecord.java | 2 +- .../securesms/groups/GroupManager.java | 2 +- .../securesms/groups/GroupManagerV2.java | 27 ++- .../securesms/groups/GroupProtoUtil.java | 17 +- .../groups/GroupsV2Authorization.java | 4 +- .../securesms/groups/LiveGroup.java | 2 +- .../groups/v2/GroupCandidateHelper.java | 2 +- .../v2/processing/GroupsV2StateProcessor.java | 14 +- .../jobs/PushGroupSilentUpdateSendJob.java | 20 +- .../securesms/mms/MessageGroupContext.java | 25 ++- .../webrtc/links/SignalCallLinkManager.kt | 6 +- .../securesms/util/ProfileUtil.java | 2 +- .../securesms/database/GV2Transformer.kt | 4 +- .../securesms/database/GroupTestUtil.kt | 6 +- .../GroupsV2UpdateMessageProducerTest.java | 2 +- .../securesms/groups/v2/ChangeBuilder.java | 4 +- .../databaseprotos/DecryptedGroupHelper.kt | 7 +- dependencies.gradle | 2 +- gradle/verification-metadata.xml | 44 +--- .../api/SignalServiceAccountManager.java | 8 +- .../api/SignalServiceMessageReceiver.java | 17 +- .../api/groupsv2/DecryptedGroupUtil.java | 135 ++++++------ .../api/groupsv2/GroupCandidate.java | 22 +- .../api/groupsv2/GroupChangeReconstruct.java | 74 +++---- .../api/groupsv2/GroupChangeUtil.java | 56 ++--- .../api/groupsv2/GroupsV2Api.java | 9 +- .../api/groupsv2/GroupsV2Operations.java | 203 ++++++++++-------- .../signalservice/api/push/ServiceId.kt | 43 ++-- .../api/services/ProfileService.java | 12 +- .../internal/push/PushServiceSocket.java | 12 +- .../src/main/proto/DecryptedGroups.proto | 18 +- .../api/groupsv2/DecryptedGroupUtilTest.java | 59 ++--- .../DecryptedGroupUtil_apply_Test.java | 2 +- .../groupsv2/GroupsV2Operations_ban_Test.java | 17 +- ...roupsV2Operations_decrypt_change_Test.java | 106 ++++----- ...GroupsV2Operations_decrypt_group_Test.java | 76 +++---- .../api/groupsv2/ProtoTestUtils.java | 6 +- .../api/groupsv2/TestZkGroupServer.java | 6 +- 44 files changed, 614 insertions(+), 593 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt index fe39811a00..e6fd722fde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt @@ -153,8 +153,8 @@ class ConversationSettingsRepository( if (groupRecord.isV2Group) { val decryptedGroup: DecryptedGroup = groupRecord.requireV2GroupProperties().decryptedGroup val pendingMembers: List = decryptedGroup.pendingMembersList - .map(DecryptedPendingMember::getUuid) - .map(GroupProtoUtil::uuidByteStringToRecipientId) + .map(DecryptedPendingMember::getServiceIdBinary) + .map(GroupProtoUtil::serviceIdBinaryToRecipientId) val members = mutableListOf() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index 9e6ba2ed6e..b61aa6d0a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Collection; @@ -291,11 +292,11 @@ private final class GroupDataManager { private final Observer updater; - private LiveGroup liveGroup; - private LiveData liveIsSelfAdmin; - private LiveData> liveBannedMembers; - private LiveData> liveFullMembers; - private Recipient conversationRecipient; + private LiveGroup liveGroup; + private LiveData liveIsSelfAdmin; + private LiveData> liveBannedMembers; + private LiveData> liveFullMembers; + private Recipient conversationRecipient; GroupDataManager() { this.updater = unused -> update(); @@ -341,9 +342,9 @@ public boolean isBanned(Recipient recipient) { return false; } - Set bannedMembers = liveBannedMembers.getValue(); + Set bannedMembers = liveBannedMembers.getValue(); if (bannedMembers != null) { - return recipient.getServiceId().isPresent() && bannedMembers.contains(recipient.requireServiceId().getRawUuid()); + return recipient.getServiceId().isPresent() && bannedMembers.contains(recipient.requireServiceId()); } return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt index eb229c6dab..cac4ca9ab7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt @@ -60,7 +60,6 @@ import org.whispersystems.signalservice.api.util.UuidUtil import java.io.Closeable import java.security.SecureRandom import java.util.Optional -import java.util.UUID import java.util.stream.Collectors import javax.annotation.CheckReturnValue @@ -861,8 +860,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT writableDatabase.withinTransaction { db -> val record = getGroup(groupIdV1).get() - val newMembers: MutableList = DecryptedGroupUtil.membersToUuidList(decryptedGroup.membersList).toRecipientIds() - val pendingMembers: List = DecryptedGroupUtil.pendingToUuidList(decryptedGroup.pendingMembersList).toRecipientIds() + val newMembers: MutableList = DecryptedGroupUtil.membersToServiceIdList(decryptedGroup.membersList).toRecipientIds() + val pendingMembers: List = DecryptedGroupUtil.pendingToServiceIdList(decryptedGroup.pendingMembersList).toRecipientIds() newMembers.addAll(pendingMembers) val droppedMembers: List = SetUtil.difference(record.members, newMembers).toList() @@ -915,11 +914,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup) - val addedMembers: Set = DecryptedGroupUtil.membersToUuidList(change.newMembersList).toRecipientIds().toSet() - val removedMembers: Set = DecryptedGroupUtil.removedMembersUuidList(change).toRecipientIds().toSet() - val addedInvites: Set = DecryptedGroupUtil.pendingToUuidList(change.newPendingMembersList).toRecipientIds().toSet() - val removedInvites: Set = DecryptedGroupUtil.removedPendingMembersUuidList(change).toRecipientIds().toSet() - val acceptedInvites: Set = DecryptedGroupUtil.membersToUuidList(change.promotePendingMembersList).toRecipientIds().toSet() + val addedMembers: Set = DecryptedGroupUtil.membersToServiceIdList(change.newMembersList).toRecipientIds().toSet() + val removedMembers: Set = DecryptedGroupUtil.removedMembersServiceIdList(change).toRecipientIds().toSet() + val addedInvites: Set = DecryptedGroupUtil.pendingToServiceIdList(change.newPendingMembersList).toRecipientIds().toSet() + val removedInvites: Set = DecryptedGroupUtil.removedPendingMembersServiceIdList(change).toRecipientIds().toSet() + val acceptedInvites: Set = DecryptedGroupUtil.membersToServiceIdList(change.promotePendingMembersList).toRecipientIds().toSet() unmigratedV1Members -= addedMembers unmigratedV1Members -= removedMembers @@ -934,7 +933,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT if (existingGroup.isPresent && existingGroup.get().isV2Group) { val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup) - val removed: List = DecryptedGroupUtil.removedMembersUuidList(change) + val removed: List = DecryptedGroupUtil.removedMembersServiceIdList(change) if (removed.isNotEmpty()) { val distributionId = existingGroup.get().distributionId!! @@ -1208,8 +1207,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT DecryptedGroup.parseFrom(decryptedGroupBytes) } - val bannedMembers: Set by lazy { - DecryptedGroupUtil.bannedMembersToUuidSet(decryptedGroup.bannedMembersList) + val bannedMembers: Set by lazy { + DecryptedGroupUtil.bannedMembersToServiceIdSet(decryptedGroup.bannedMembersList) } fun isAdmin(recipient: Recipient): Boolean { @@ -1243,7 +1242,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } if (memberLevel.isAbsent()) { - memberLevel = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, serviceId.get().rawUuid) + memberLevel = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembersList, serviceId.get()) .map { MemberLevel.PENDING_MEMBER } } @@ -1265,7 +1264,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT fun getMemberRecipientIds(memberSet: MemberSet): List { val includeSelf = memberSet.includeSelf - val selfAciUuid = SignalStore.account().requireAci().rawUuid + val selfAci = SignalStore.account().requireAci() + val selfAciUuid = selfAci.rawUuid val recipients: MutableList = ArrayList(decryptedGroup.membersCount + decryptedGroup.pendingMembersCount) var unknownMembers = 0 @@ -1280,11 +1280,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } if (memberSet.includePending) { - for (uuid in DecryptedGroupUtil.pendingToUuidList(decryptedGroup.pendingMembersList)) { - if (UuidUtil.UNKNOWN_UUID == uuid) { + for (serviceId in DecryptedGroupUtil.pendingToServiceIdList(decryptedGroup.pendingMembersList)) { + if (serviceId.isUnknown) { unknownPending++ - } else if (includeSelf || selfAciUuid != uuid) { - recipients += RecipientId.from(ACI.from(uuid)) + } else if (includeSelf || selfAci != serviceId) { + recipients += RecipientId.from(serviceId) } } } @@ -1378,11 +1378,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT val aci = SignalStore.account().requireAci() return DecryptedGroupUtil.findMemberByUuid(decryptedGroup.membersList, aci.rawUuid).isPresent || - DecryptedGroupUtil.findPendingByUuid(decryptedGroup.pendingMembersList, aci.rawUuid).isPresent + DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembersList, aci).isPresent } - private fun List.toRecipientIds(): MutableList { - return uuidsToRecipientIds(this) + private fun List.toRecipientIds(): MutableList { + return serviceIdsToRecipientIds(this.asSequence()) } private fun Collection.serialize(): String { @@ -1398,15 +1398,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } } - private fun uuidsToRecipientIds(uuids: List): MutableList { - return uuids - .asSequence() - .map { uuid -> - if (uuid == UuidUtil.UNKNOWN_UUID) { + private fun serviceIdsToRecipientIds(serviceIds: Sequence): MutableList { + return serviceIds + .map { serviceId -> + if (serviceId.isUnknown) { Log.w(TAG, "Saw an unknown UUID when mapping to RecipientIds!") null } else { - val id = RecipientId.from(ACI.from(uuid)) + val id = RecipientId.from(serviceId) val remapped = RemappedRecords.getInstance().getRecipient(id) if (remapped.isPresent) { Log.w(TAG, "Saw that $id remapped to $remapped. Using the mapping.") @@ -1422,8 +1421,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } private fun getV2GroupMembers(decryptedGroup: DecryptedGroup, shouldRetry: Boolean): List { - val uuids: List = DecryptedGroupUtil.membersToUuidList(decryptedGroup.membersList) - val ids: List = uuidsToRecipientIds(uuids) + val ids: List = DecryptedGroupUtil.membersToServiceIdList(decryptedGroup.membersList).toRecipientIds() return if (RemappedRecords.getInstance().areAnyRemapped(ids)) { if (shouldRetry) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 1bd13d5684..b1417a8495 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -142,7 +142,6 @@ import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.isStory import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage import org.whispersystems.signalservice.api.push.ServiceId -import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage import java.io.Closeable import java.io.IOException @@ -3089,10 +3088,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat val members: MutableSet = mutableSetOf() if (message.isGroupUpdate && message.isV2Group) { - // TODO [greyson][ServiceId] pending members could be ACI's or PNI's members += message.requireGroupV2Properties().allActivePendingAndRemovedMembers .distinct() - .map { uuid -> RecipientId.from(ACI.from(uuid)) } + .map { serviceId -> RecipientId.from(serviceId) } .toList() members -= Recipient.self().id diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt index 3113866d89..387c11a09b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupRecord.kt @@ -176,7 +176,7 @@ class GroupRecord( if (isV2Group) { val serviceId = recipient.serviceId if (serviceId.isPresent) { - return DecryptedGroupUtil.findPendingByUuid(requireV2GroupProperties().decryptedGroup.pendingMembersList, serviceId.get().rawUuid) + return DecryptedGroupUtil.findPendingByServiceId(requireV2GroupProperties().decryptedGroup.pendingMembersList, serviceId.get()) .isPresent } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index 684d3bb3a9..91a33d4b9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.SpanUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -72,9 +73,9 @@ final class GroupsV2UpdateMessageProducer { * When the revision of the group is 0, the change is very noisy and only the editor is useful. */ UpdateDescription describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange decryptedGroupChange) { - Optional selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getAci().getRawUuid()); + Optional selfPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfIds.getAci()); if (!selfPending.isPresent() && selfIds.getPni() != null) { - selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getPni().getRawUuid()); + selfPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfIds.getPni()); } if (selfPending.isPresent()) { @@ -310,13 +311,13 @@ private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull int notYouInviteCount = 0; for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) { - boolean newMemberIsYou = selfIds.matches(invitee.getUuid()); + boolean newMemberIsYou = selfIds.matches(invitee.getServiceIdBinary()); if (newMemberIsYou) { updates.add(0, updateDescription(R.string.MessageRecord_s_invited_you_to_the_group, change.getEditor(), R.drawable.ic_update_group_add_16)); } else { if (editorIsYou) { - updates.add(updateDescription(R.string.MessageRecord_you_invited_s_to_the_group, invitee.getUuid(), R.drawable.ic_update_group_add_16)); + updates.add(updateDescription(R.string.MessageRecord_you_invited_s_to_the_group, invitee.getServiceIdBinary(), R.drawable.ic_update_group_add_16)); } else { notYouInviteCount++; } @@ -332,7 +333,7 @@ private void describeUnknownEditorInvitations(@NonNull DecryptedGroupChange chan int notYouInviteCount = 0; for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) { - boolean newMemberIsYou = selfIds.matches(invitee.getUuid()); + boolean newMemberIsYou = selfIds.matches(invitee.getServiceIdBinary()); if (newMemberIsYou) { UUID uuid = UuidUtil.fromByteStringOrUnknown(invitee.getAddedByUuid()); @@ -357,14 +358,14 @@ private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @N int notDeclineCount = 0; for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) { - boolean decline = invitee.getUuid().equals(change.getEditor()); + boolean decline = invitee.getServiceIdBinary().equals(change.getEditor()); if (decline) { if (editorIsYou) { updates.add(updateDescription(context.getString(R.string.MessageRecord_you_declined_the_invitation_to_the_group), R.drawable.ic_update_group_decline_16)); } else { updates.add(updateDescription(context.getString(R.string.MessageRecord_someone_declined_an_invitation_to_the_group), R.drawable.ic_update_group_decline_16)); } - } else if (selfIds.matches(invitee.getUuid())) { + } else if (selfIds.matches(invitee.getServiceIdBinary())) { updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_invitation_to_the_group, change.getEditor(), R.drawable.ic_update_group_decline_16)); } else { notDeclineCount++; @@ -384,7 +385,7 @@ private void describeUnknownEditorRevokedInvitations(@NonNull DecryptedGroupChan int notDeclineCount = 0; for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) { - boolean inviteeWasYou = selfIds.matches(invitee.getUuid()); + boolean inviteeWasYou = selfIds.matches(invitee.getServiceIdBinary()); if (inviteeWasYou) { updates.add(updateDescription(context.getString(R.string.MessageRecord_an_admin_revoked_your_invitation_to_the_group), R.drawable.ic_update_group_decline_16)); @@ -817,14 +818,14 @@ private static UpdateDescription updateDescription(@NonNull String string, @Draw } private UpdateDescription updateDescription(@StringRes int stringRes, - @NonNull ByteString uuid1Bytes, + @NonNull ByteString serviceId1Bytes, @DrawableRes int iconResource) { - ACI aci = ACI.parseOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(aci); + ACI serviceId = ACI.parseOrUnknown(serviceId1Bytes); + RecipientId recipientId = RecipientId.from(serviceId); return UpdateDescription.mentioning( - Collections.singletonList(aci), + Collections.singletonList(serviceId), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null)); @@ -835,18 +836,18 @@ private UpdateDescription updateDescription(@StringRes int stringRes, } private UpdateDescription updateDescription(@StringRes int stringRes, - @NonNull ByteString uuid1Bytes, - @NonNull ByteString uuid2Bytes, + @NonNull ByteString serviceId1Bytes, + @NonNull ByteString serviceId2Bytes, @DrawableRes int iconResource) { - ACI aci1 = ACI.parseOrUnknown(uuid1Bytes); - ACI aci2 = ACI.parseOrUnknown(uuid2Bytes); + ACI serviceId1 = ACI.parseOrUnknown(serviceId1Bytes); + ACI serviceId2 = ACI.parseOrUnknown(serviceId2Bytes); - RecipientId recipientId1 = RecipientId.from(aci1); - RecipientId recipientId2 = RecipientId.from(aci2); + RecipientId recipientId1 = RecipientId.from(serviceId1); + RecipientId recipientId2 = RecipientId.from(serviceId2); return UpdateDescription.mentioning( - Arrays.asList(aci1, aci2), + Arrays.asList(serviceId1, serviceId2), () -> { List recipientIdList = Arrays.asList(recipientId1, recipientId2); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null)); @@ -858,15 +859,15 @@ private UpdateDescription updateDescription(@StringRes int stringRes, } private UpdateDescription updateDescription(@StringRes int stringRes, - @NonNull ByteString uuid1Bytes, + @NonNull ByteString serviceId1Bytes, @NonNull Object formatArg, @DrawableRes int iconResource) { - ACI aci = ACI.parseOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(aci); + ACI serviceId = ACI.parseOrUnknown(serviceId1Bytes); + RecipientId recipientId = RecipientId.from(serviceId); return UpdateDescription.mentioning( - Collections.singletonList(aci), + Collections.singletonList(serviceId), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, Collections.singletonList(formatArg))); @@ -879,15 +880,15 @@ private UpdateDescription updateDescription(@StringRes int stringRes, private UpdateDescription updateDescription(@PluralsRes int stringRes, int quantity, - @NonNull ByteString uuid1Bytes, + @NonNull ByteString serviceId1Bytes, @NonNull Object formatArg, @DrawableRes int iconResource) { - ACI aci = ACI.parseOrUnknown(uuid1Bytes); - RecipientId recipientId = RecipientId.from(aci); + ACI serviceId = ACI.parseOrUnknown(serviceId1Bytes); + RecipientId recipientId = RecipientId.from(serviceId); return UpdateDescription.mentioning( - Collections.singletonList(aci), + Collections.singletonList(serviceId), () -> { List recipientIdList = Collections.singletonList(recipientId); String templateString = context.getResources().getQuantityString(stringRes, quantity, makePlaceholders(recipientIdList, Collections.singletonList(formatArg))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index b6d3fa4964..7d1f33b42b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -346,7 +346,7 @@ private static boolean selfCreatedGroup(@NonNull DecryptedGroupChange change) { } DecryptedGroup groupState = decryptedGroupV2Context.getGroupState(); - boolean invited = DecryptedGroupUtil.findPendingByUuid(groupState.getPendingMembersList(), SignalStore.account().requireAci().getRawUuid()).isPresent(); + boolean invited = DecryptedGroupUtil.findPendingByServiceId(groupState.getPendingMembersList(), SignalStore.account().requireAci()).isPresent(); if (decryptedGroupV2Context.hasChange()) { UUID changeEditor = UuidUtil.fromByteStringOrNull(decryptedGroupV2Context.getChange().getEditor()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 1a80182132..35f66a1fa3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -336,7 +336,7 @@ public static void unban(@NonNull Context context, throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException { try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().getRawUuid())); + editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId())); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 6a113dafcc..795dccd57f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -176,7 +176,7 @@ final class GroupManagerV2 { Map uuidCipherTexts = new HashMap<>(); for (Recipient recipient : recipients) { - uuidCipherTexts.put(recipient.requireServiceId().getRawUuid(), clientZkGroupCipher.encryptUuid(recipient.requireServiceId().getRawUuid())); + uuidCipherTexts.put(recipient.requireServiceId().getRawUuid(), clientZkGroupCipher.encrypt(recipient.requireServiceId().getLibSignalServiceId())); } return uuidCipherTexts; @@ -343,7 +343,7 @@ final class GroupEditor extends LockOwner { } @WorkerThread - @NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection newMembers, @NonNull Set bannedMembers) + @NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection newMembers, @NonNull Set bannedMembers) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, MembershipNotSuitableForV2Exception { if (!GroupsV2CapabilityChecker.allHaveServiceId(newMembers)) { @@ -356,7 +356,7 @@ final class GroupEditor extends LockOwner { groupCandidates = GroupCandidate.withoutExpiringProfileKeyCredentials(groupCandidates); } - return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.getRawUuid())); + return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci)); } @WorkerThread @@ -454,7 +454,7 @@ final class GroupEditor extends LockOwner { throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { Recipient recipient = Recipient.resolved(recipientId); - return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMemberRole(recipient.requireServiceId().getRawUuid(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT)); + return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMemberRole(recipient.requireAci(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT)); } @WorkerThread @@ -464,8 +464,8 @@ void leaveGroup(boolean sendToMembers) GroupRecord groupRecord = groupDatabase.requireGroup(groupId); DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup(); Optional selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.getRawUuid()); - Optional aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.getRawUuid()); - Optional pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.getRawUuid()); + Optional aciPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.getPendingMembersList(), selfAci); + Optional pniPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.getPendingMembersList(), selfPni); Optional selfPendingMember = Optional.empty(); ServiceId serviceId = selfAci; @@ -555,8 +555,8 @@ void leaveGroup(boolean sendToMembers) return null; } - Optional aciInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfAci.getRawUuid()); - Optional pniInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfPni.getRawUuid()); + Optional aciInPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfAci); + Optional pniInPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfPni); GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); @@ -584,10 +584,10 @@ public GroupManager.GroupActionResult ban(UUID uuid) return commitChangeWithConflictResolution(selfAci, groupOperations.createBanUuidsChange(Collections.singleton(uuid), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList())); } - public GroupManager.GroupActionResult unban(Set uuids) + public GroupManager.GroupActionResult unban(Set serviceIds) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { - return commitChangeWithConflictResolution(selfAci, groupOperations.createUnbanUuidsChange(uuids)); + return commitChangeWithConflictResolution(selfAci, groupOperations.createUnbanServiceIdsChange(serviceIds)); } @WorkerThread @@ -704,7 +704,7 @@ private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServi GroupChange.Actions changeActions = change.build(); return GroupChangeUtil.resolveConflict(groupUpdateResult.getLatestServer(), - groupOperations.decryptChange(changeActions, authServiceId.getRawUuid()), + groupOperations.decryptChange(changeActions, authServiceId), changeActions); } catch (VerificationFailedException | InvalidGroupStateException ex) { throw new GroupChangeFailedException(ex); @@ -1331,9 +1331,8 @@ static class SendGroupUpdateHelper { } private static @NonNull List getPendingMemberRecipientIds(@NonNull List newPendingMembersList) { - // TODO [greyson][ServiceId] Pending members can be ACI's or PNI's - return Stream.of(DecryptedGroupUtil.pendingToUuidList(newPendingMembersList)) - .map(uuid -> RecipientId.from(ACI.from(uuid))) + return Stream.of(DecryptedGroupUtil.pendingToServiceIdList(newPendingMembersList)) + .map(serviceId -> RecipientId.from(serviceId)) .toList(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java index 7c06d58f3c..6351e0012d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupProtoUtil.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; @@ -30,17 +31,17 @@ public final class GroupProtoUtil { private GroupProtoUtil() { } - public static int findRevisionWeWereAdded(@NonNull PartialDecryptedGroup partialDecryptedGroup, @NonNull UUID uuid) + public static int findRevisionWeWereAdded(@NonNull PartialDecryptedGroup partialDecryptedGroup, @NonNull ACI self) throws GroupNotAMemberException { - ByteString bytes = UuidUtil.toByteString(uuid); + ByteString bytes = self.toByteString(); for (DecryptedMember decryptedMember : partialDecryptedGroup.getMembersList()) { if (decryptedMember.getUuid().equals(bytes)) { return decryptedMember.getJoinedAtRevision(); } } for (DecryptedPendingMember decryptedMember : partialDecryptedGroup.getPendingMembersList()) { - if (decryptedMember.getUuid().equals(bytes)) { + if (decryptedMember.getServiceIdBinary().equals(bytes)) { // Assume latest, we don't have any information about when pending members were invited return partialDecryptedGroup.getRevision(); } @@ -80,12 +81,12 @@ public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull Gro @WorkerThread public static Recipient pendingMemberToRecipient(@NonNull Context context, @NonNull DecryptedPendingMember pendingMember) { - return pendingMemberServiceIdToRecipient(context, pendingMember.getUuid()); + return pendingMemberServiceIdToRecipient(context, pendingMember.getServiceIdBinary()); } @WorkerThread - public static Recipient pendingMemberServiceIdToRecipient(@NonNull Context context, @NonNull ByteString uuidByteString) { - ServiceId serviceId = ServiceId.parseOrThrow(uuidByteString); + public static Recipient pendingMemberServiceIdToRecipient(@NonNull Context context, @NonNull ByteString serviceIdBinary) { + ServiceId serviceId = ServiceId.parseOrThrow(serviceIdBinary); if (serviceId.isUnknown()) { return Recipient.UNKNOWN; @@ -95,8 +96,8 @@ public static Recipient pendingMemberServiceIdToRecipient(@NonNull Context conte } @WorkerThread - public static @NonNull RecipientId uuidByteStringToRecipientId(@NonNull ByteString uuidByteString) { - ServiceId serviceId = ServiceId.parseOrThrow(uuidByteString); + public static @NonNull RecipientId serviceIdBinaryToRecipientId(@NonNull ByteString serviceIdBinary) { + ServiceId serviceId = ServiceId.parseOrThrow(serviceIdBinary); if (serviceId.isUnknown()) { return RecipientId.UNKNOWN; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java index 4c4de6a521..6977dcb92f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2Authorization.java @@ -126,13 +126,13 @@ private CallLinkAuthCredentialPresentation getCallLinkAuthCredentialPresentation } CallLinkAuthCredential credential = authCredentialResponse.receive( - Recipient.self().requireServiceId().getRawUuid(), + Recipient.self().requireAci().getLibSignalAci(), Instant.ofEpochSecond(todaySeconds), genericServerPublicParams ); return credential.present( - Recipient.self().requireServiceId().getRawUuid(), + Recipient.self().requireAci().getLibSignalAci(), Instant.ofEpochSecond(todaySeconds), genericServerPublicParams, callLinkSecretParams diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java b/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java index e41287a6f0..51c2f39377 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/LiveGroup.java @@ -144,7 +144,7 @@ public LiveData isSelfAdmin() { return Transformations.map(groupRecord, g -> g.isAdmin(Recipient.self())); } - public LiveData> getBannedMembers() { + public LiveData> getBannedMembers() { return Transformations.map(groupRecord, g -> g.isV2Group() ? g.requireV2GroupProperties().getBannedMembers() : Collections.emptySet()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java index 8344331aa2..8421819cf5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java @@ -51,7 +51,7 @@ public GroupCandidateHelper() { } Optional expiringProfileKeyCredential = Optional.ofNullable(recipient.getExpiringProfileKeyCredential()); - GroupCandidate candidate = new GroupCandidate(serviceId.getRawUuid(), expiringProfileKeyCredential); + GroupCandidate candidate = new GroupCandidate(serviceId, expiringProfileKeyCredential); if (!candidate.hasValidProfileKeyCredential()) { recipientTable.clearProfileKeyCredential(recipient.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index aba0807edc..409e408aa7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -380,9 +380,7 @@ private boolean notInGroupAndNotBeingAdded(@NonNull Optional localR boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList() .stream() - .map(DecryptedPendingMember::getUuid) - .map(UuidUtil::fromByteStringOrNull) - .filter(Objects::nonNull) + .map(DecryptedPendingMember::getServiceIdBinary) .anyMatch(serviceIds::matches); boolean addedAsRequestingMember = signedGroupChange.getNewRequestingMembersList() @@ -398,9 +396,7 @@ private boolean notInGroupAndNotBeingAdded(@NonNull Optional localR private boolean notHavingInviteRevoked(@NonNull DecryptedGroupChange signedGroupChange) { boolean havingInviteRevoked = signedGroupChange.getDeletePendingMembersList() .stream() - .map(DecryptedPendingMemberRemoval::getUuid) - .map(UuidUtil::fromByteStringOrNull) - .filter(Objects::nonNull) + .map(DecryptedPendingMemberRemoval::getServiceIdBinary) .anyMatch(serviceIds::matches); return !havingInviteRevoked; @@ -434,7 +430,7 @@ private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, Decrypte info("Latest revision or not a member, use latest only"); inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null))); } else { - int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci().getRawUuid()); + int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci()); int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded; boolean includeFirstState = forceIncludeFirst || @@ -694,7 +690,7 @@ void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull } Optional selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), aci.getRawUuid()); - Optional selfAsPendingOptional = DecryptedGroupUtil.findPendingByUuid(newLocalState.getPendingMembersList(), aci.getRawUuid()); + Optional selfAsPendingOptional = DecryptedGroupUtil.findPendingByServiceId(newLocalState.getPendingMembersList(), aci); if (selfAsMemberOptional.isPresent()) { DecryptedMember selfAsMember = selfAsMemberOptional.get(); @@ -835,7 +831,7 @@ private Optional getEditor(@NonNull DecryptedGroupV2Context decryptedGroup if (changeEditor.isPresent()) { return changeEditor; } else { - Optional pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), aci.getRawUuid()); + Optional pendingByUuid = DecryptedGroupUtil.findPendingByServiceId(decryptedGroupV2Context.getGroupState().getPendingMembersList(), aci); if (pendingByUuid.isPresent()) { return Optional.ofNullable(UuidUtil.fromByteStringOrNull(pendingByUuid.get().getAddedByUuid())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java index 6fe3e6e401..43f374823c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java @@ -33,6 +33,7 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -72,13 +73,18 @@ public final class PushGroupSilentUpdateSendJob extends BaseJob { @NonNull DecryptedGroup decryptedGroup, @NonNull OutgoingMessage groupMessage) { - List memberUuids = DecryptedGroupUtil.toUuidList(decryptedGroup.getMembersList()); - List pendingUuids = DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList()); - - Set recipients = Stream.concat(Stream.of(memberUuids), Stream.of(pendingUuids)) - .filter(uuid -> !UuidUtil.UNKNOWN_UUID.equals(uuid)) - .filter(uuid -> !SignalStore.account().requireAci().getRawUuid().equals(uuid)) - .map(uuid -> Recipient.externalPush(ACI.from(uuid))) + List memberUuids = DecryptedGroupUtil.toUuidList(decryptedGroup.getMembersList()); + List pendingServiceIds = DecryptedGroupUtil.pendingToServiceIdList(decryptedGroup.getPendingMembersList()); + + Stream memberServiceIds = Stream.of(memberUuids) + .filter(uuid -> !UuidUtil.UNKNOWN_UUID.equals(uuid)) + .filter(uuid -> !SignalStore.account().requireAci().getRawUuid().equals(uuid)) + .map(ACI::from); + Stream filteredPendingServiceIds = Stream.of(pendingServiceIds) + .filterNot(ServiceId::isUnknown); + + Set recipients = Stream.concat(memberServiceIds, filteredPendingServiceIds) + .map(serviceId -> Recipient.externalPush(serviceId)) .filter(recipient -> recipient.getRegistered() != RecipientTable.RegisteredState.NOT_REGISTERED) .map(Recipient::getId) .collect(Collectors.toSet()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java b/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java index 851d716439..b3b9f48e8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/MessageGroupContext.java @@ -166,19 +166,18 @@ private GroupV2Properties(DecryptedGroupV2Context decryptedGroupV2Context) { return decryptedGroupV2Context.getChange(); } - public @NonNull List getAllActivePendingAndRemovedMembers() { - LinkedList memberUuids = new LinkedList<>(); - DecryptedGroup groupState = decryptedGroupV2Context.getGroupState(); - DecryptedGroupChange groupChange = decryptedGroupV2Context.getChange(); - - memberUuids.addAll(DecryptedGroupUtil.membersToUuidList(groupState.getMembersList())); - memberUuids.addAll(DecryptedGroupUtil.pendingToUuidList(groupState.getPendingMembersList())); - - memberUuids.addAll(DecryptedGroupUtil.removedMembersUuidList(groupChange)); - memberUuids.addAll(DecryptedGroupUtil.removedPendingMembersUuidList(groupChange)); - memberUuids.addAll(DecryptedGroupUtil.removedRequestingMembersUuidList(groupChange)); - - return UuidUtil.filterKnown(memberUuids); + public @NonNull List getAllActivePendingAndRemovedMembers() { + DecryptedGroup groupState = decryptedGroupV2Context.getGroupState(); + DecryptedGroupChange groupChange = decryptedGroupV2Context.getChange(); + + return Stream.of(DecryptedGroupUtil.membersToServiceIdList(groupState.getMembersList()), + DecryptedGroupUtil.pendingToServiceIdList(groupState.getPendingMembersList()), + DecryptedGroupUtil.removedMembersServiceIdList(groupChange), + DecryptedGroupUtil.removedPendingMembersServiceIdList(groupChange), + DecryptedGroupUtil.removedRequestingMembersServiceIdList(groupChange)) + .flatMap(Stream::of) + .filterNot(ServiceId::isUnknown) + .toList(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt index d1fc094b44..f44a175f30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/links/SignalCallLinkManager.kt @@ -45,7 +45,7 @@ class SignalCallLinkManager( linkRootKey: ByteArray, roomId: ByteArray ): CreateCallLinkCredentialPresentation { - val userUuid = Recipient.self().requireAci().rawUuid + val userAci = Recipient.self().requireAci() val requestContext = CreateCallLinkCredentialRequestContext.forRoom(roomId) val request = requestContext.request @@ -60,7 +60,7 @@ class SignalCallLinkManager( val createCallLinkCredential: CreateCallLinkCredential = requestContext.receiveResponse( serviceResponse.result.get(), - userUuid, + userAci.libSignalAci, genericServerPublicParams ) @@ -68,7 +68,7 @@ class SignalCallLinkManager( return createCallLinkCredential.present( roomId, - userUuid, + userAci.libSignalAci, genericServerPublicParams, CallLinkSecretParams.deriveFromRootKey(linkRootKey) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ProfileUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ProfileUtil.java index 1de5a2b211..90958d066a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ProfileUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ProfileUtil.java @@ -295,7 +295,7 @@ public static Optional updateExpiringProfileKeyCre Log.i(TAG, String.format("Updating profile key credential on recipient %s, fetching", recipient.getId())); Optional profileKeyCredentialOptional = ApplicationDependencies.getSignalServiceAccountManager() - .resolveProfileKeyCredential(recipient.requireServiceId(), profileKey, Locale.getDefault()); + .resolveProfileKeyCredential(recipient.requireAci(), profileKey, Locale.getDefault()); if (profileKeyCredentialOptional.isPresent()) { boolean updatedProfileKey = SignalDatabase.recipients().setProfileKeyCredential(recipient.getId(), profileKey, profileKeyCredentialOptional.get()); diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt b/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt index 03ccbfc08d..a1eee10c3b 100644 --- a/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt +++ b/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt @@ -29,9 +29,9 @@ object GV2Transformer : ColumnTransformer { private fun DecryptedGroup.formatAsHtml(): String { val members: String = describeList(membersList, DecryptedMember::getUuid) - val pending: String = describeList(pendingMembersList, DecryptedPendingMember::getUuid) + val pending: String = describeList(pendingMembersList, DecryptedPendingMember::getServiceIdBinary) val requesting: String = describeList(requestingMembersList, DecryptedRequestingMember::getUuid) - val banned: String = describeList(bannedMembersList, DecryptedBannedMember::getUuid) + val banned: String = describeList(bannedMembersList, DecryptedBannedMember::getServiceIdBinary) return """ Revision: $revision diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt b/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt index d82bf1c830..da9eb471a0 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/database/GroupTestUtil.kt @@ -85,15 +85,15 @@ class GroupChangeData(private val revision: Int, private val groupOperations: Gr } fun source(serviceId: ServiceId) { - actionsBuilder.sourceUuid = groupOperations.encryptUuid(serviceId.rawUuid) + actionsBuilder.sourceUuid = groupOperations.encryptServiceId(serviceId) } fun deleteMember(serviceId: ServiceId) { - actionsBuilder.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(groupOperations.encryptUuid(serviceId.rawUuid))) + actionsBuilder.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().setDeletedUserId(groupOperations.encryptServiceId(serviceId))) } fun modifyRole(serviceId: ServiceId, role: Member.Role) { - actionsBuilder.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(groupOperations.encryptUuid(serviceId.rawUuid)).setRole(role)) + actionsBuilder.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().setUserId(groupOperations.encryptServiceId(serviceId)).setRole(role)) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java index c0c7cba479..05791d2d1b 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java @@ -1475,7 +1475,7 @@ private static class GroupStateBuilder { GroupStateBuilder invite(@NonNull UUID inviter, @NonNull UUID invitee) { builder.addPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(invitee)) + .setServiceIdBinary(UuidUtil.toByteString(invitee)) .setAddedByUuid(UuidUtil.toByteString(inviter))); return this; } diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java index a0cf08119f..b40e95751d 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java @@ -85,14 +85,14 @@ public ChangeBuilder invite(@NonNull UUID potentialMember) { public ChangeBuilder inviteBy(@NonNull UUID potentialMember, @NonNull UUID inviter) { builder.addNewPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(potentialMember)) + .setServiceIdBinary(UuidUtil.toByteString(potentialMember)) .setAddedByUuid(UuidUtil.toByteString(inviter))); return this; } public ChangeBuilder uninvite(@NonNull UUID pendingMember) { builder.addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(pendingMember))); + .setServiceIdBinary(UuidUtil.toByteString(pendingMember))); return this; } diff --git a/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt b/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt index b6ff71b053..b7b8d488ba 100644 --- a/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt +++ b/app/src/testShared/org/thoughtcrime/securesms/database/model/databaseprotos/DecryptedGroupHelper.kt @@ -9,7 +9,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId.ACI -import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.SignalServiceProtos import java.util.UUID @@ -43,10 +42,6 @@ fun DecryptedGroupChange.Builder.addMember(aci: ACI) { addNewMembers(member(aci)) } -fun ServiceId.toByteString(): ByteString { - return UuidUtil.toByteString(rawUuid) -} - fun member(serviceId: UUID, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0): DecryptedMember { return member(ACI.from(serviceId), role, joinedAt) } @@ -67,6 +62,6 @@ fun requestingMember(serviceId: ServiceId): DecryptedRequestingMember { fun pendingMember(serviceId: ServiceId): DecryptedPendingMember { return DecryptedPendingMember.newBuilder() - .setUuid(serviceId.toByteString()) + .setServiceIdBinary(serviceId.toByteString()) .build() } diff --git a/dependencies.gradle b/dependencies.gradle index eb4caa57a9..60511a9b95 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -14,7 +14,7 @@ dependencyResolutionManagement { version('exoplayer', '2.19.0') version('glide', '4.13.2') version('kotlin', '1.8.10') - version('libsignal-client', '0.28.1') + version('libsignal-client', '0.30.0') version('mp4parser', '1.9.39') version('android-gradle-plugin', '8.0.2') version('accompanist', '0.28.0') diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 15f8e75895..29f3edb7ac 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4992,44 +4992,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 117f73b1fb..f734e48e80 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -740,23 +740,23 @@ public Optional setVersionedProfile(ACI aci, new ProfileCipherOutputStreamFactory(profileKey)); } - return this.pushServiceSocket.writeProfile(new SignalServiceProfileWrite(profileKey.getProfileKeyVersion(aci.getRawUuid()).serialize(), + return this.pushServiceSocket.writeProfile(new SignalServiceProfileWrite(profileKey.getProfileKeyVersion(aci.getLibSignalAci()).serialize(), ciphertextName, ciphertextAbout, ciphertextEmoji, ciphertextMobileCoinAddress, avatar.hasAvatar, avatar.keepTheSame, - profileKey.getCommitment(aci.getRawUuid()).serialize(), + profileKey.getCommitment(aci.getLibSignalAci()).serialize(), visibleBadgeIds), profileAvatarData); } - public Optional resolveProfileKeyCredential(ServiceId serviceId, ProfileKey profileKey, Locale locale) + public Optional resolveProfileKeyCredential(ACI serviceId, ProfileKey profileKey, Locale locale) throws NonSuccessfulResponseCodeException, PushNetworkException { try { - ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId.getRawUuid(), profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS); + ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId, profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS); return credential.getExpiringProfileKeyCredential(); } catch (InterruptedException | TimeoutException e) { throw new PushNetworkException(e); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 61eda77ff6..c8450ee2ce 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -20,6 +20,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.util.CredentialsProvider; @@ -34,6 +35,7 @@ import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers; import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture; +import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture; import org.whispersystems.signalservice.internal.websocket.ResponseMapper; import java.io.File; @@ -96,13 +98,22 @@ public ListenableFuture retrieveProfile(SignalServiceAddre SignalServiceProfile.RequestType requestType, Locale locale) { - ServiceId serviceId = address.getServiceId(); if (profileKey.isPresent()) { + ACI aci; + if (address.getServiceId() instanceof ACI) { + aci = (ACI) address.getServiceId(); + } else { + // We shouldn't ever have a profile key for a non-ACI. + SettableFuture result = new SettableFuture<>(); + result.setException(new ClassCastException("retrieving a versioned profile requires an ACI")); + return result; + } + if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { - return socket.retrieveVersionedProfileAndCredential(serviceId.getRawUuid(), profileKey.get(), unidentifiedAccess, locale); + return socket.retrieveVersionedProfileAndCredential(aci, profileKey.get(), unidentifiedAccess, locale); } else { - return FutureTransformers.map(socket.retrieveVersionedProfile(serviceId.getRawUuid(), profileKey.get(), unidentifiedAccess, locale), profile -> { + return FutureTransformers.map(socket.retrieveVersionedProfile(aci, profileKey.get(), unidentifiedAccess, locale), profile -> { return new ProfileAndCredential(profile, SignalServiceProfile.RequestType.PROFILE, Optional.empty()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java index bbd879a09f..502432ff14 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java @@ -15,6 +15,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval; import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; import org.signal.storageservice.protos.groups.local.EnabledState; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIds; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -28,6 +29,8 @@ import java.util.Set; import java.util.UUID; +import javax.annotation.Nullable; + public final class DecryptedGroupUtil { private static final String TAG = DecryptedGroupUtil.class.getSimpleName(); @@ -42,18 +45,18 @@ public static ArrayList toUuidList(Collection membersList return uuidList; } - public static ArrayList membersToUuidList(Collection membersList) { - ArrayList uuidList = new ArrayList<>(membersList.size()); + public static ArrayList membersToServiceIdList(Collection membersList) { + ArrayList serviceIdList = new ArrayList<>(membersList.size()); for (DecryptedMember member : membersList) { - UUID uuid = toUuid(member); + ServiceId serviceId = ServiceId.parseOrNull(member.getUuid()); - if (!UuidUtil.UNKNOWN_UUID.equals(uuid)) { - uuidList.add(uuid); + if (serviceId != null) { + serviceIdList.add(serviceId); } } - return uuidList; + return serviceIdList; } public static Set membersToUuidByteStringSet(Collection membersList) { @@ -67,98 +70,94 @@ public static Set membersToUuidByteStringSet(Collection pendingToUuidList(Collection membersList) { - ArrayList uuidList = new ArrayList<>(membersList.size()); + public static ArrayList pendingToServiceIdList(Collection membersList) { + ArrayList serviceIdList = new ArrayList<>(membersList.size()); for (DecryptedPendingMember member : membersList) { - uuidList.add(toUuid(member)); + ServiceId serviceId = ServiceId.parseOrNull(member.getServiceIdBinary()); + if (serviceId != null) { + serviceIdList.add(serviceId); + } else { + serviceIdList.add(ServiceId.ACI.UNKNOWN); + } } - return uuidList; + return serviceIdList; } /** * Will not return any non-decryptable member UUIDs. */ - public static ArrayList removedMembersUuidList(DecryptedGroupChange groupChange) { - List deletedMembers = groupChange.getDeleteMembersList(); - ArrayList uuidList = new ArrayList<>(deletedMembers.size()); + public static ArrayList removedMembersServiceIdList(DecryptedGroupChange groupChange) { + List deletedMembers = groupChange.getDeleteMembersList(); + ArrayList serviceIdList = new ArrayList<>(deletedMembers.size()); for (ByteString member : deletedMembers) { - UUID uuid = toUuid(member); + ServiceId serviceId = ServiceId.parseOrNull(member); - if (!UuidUtil.UNKNOWN_UUID.equals(uuid)) { - uuidList.add(uuid); + if (serviceId != null) { + serviceIdList.add(serviceId); } } - return uuidList; + return serviceIdList; } /** * Will not return any non-decryptable member UUIDs. */ - public static ArrayList removedPendingMembersUuidList(DecryptedGroupChange groupChange) { + public static ArrayList removedPendingMembersServiceIdList(DecryptedGroupChange groupChange) { List deletedPendingMembers = groupChange.getDeletePendingMembersList(); - ArrayList uuidList = new ArrayList<>(deletedPendingMembers.size()); + ArrayList serviceIdList = new ArrayList<>(deletedPendingMembers.size()); for (DecryptedPendingMemberRemoval member : deletedPendingMembers) { - UUID uuid = toUuid(member.getUuid()); + ServiceId serviceId = ServiceId.parseOrNull(member.getServiceIdBinary()); - if(!UuidUtil.UNKNOWN_UUID.equals(uuid)) { - uuidList.add(uuid); + if(serviceId != null) { + serviceIdList.add(serviceId); } } - return uuidList; + return serviceIdList; } /** * Will not return any non-decryptable member UUIDs. */ - public static ArrayList removedRequestingMembersUuidList(DecryptedGroupChange groupChange) { - List deleteRequestingMembers = groupChange.getDeleteRequestingMembersList(); - ArrayList uuidList = new ArrayList<>(deleteRequestingMembers.size()); + public static ArrayList removedRequestingMembersServiceIdList(DecryptedGroupChange groupChange) { + List deleteRequestingMembers = groupChange.getDeleteRequestingMembersList(); + ArrayList serviceIdList = new ArrayList<>(deleteRequestingMembers.size()); for (ByteString member : deleteRequestingMembers) { - UUID uuid = toUuid(member); + ServiceId serviceId = ServiceId.parseOrNull(member); - if(!UuidUtil.UNKNOWN_UUID.equals(uuid)) { - uuidList.add(uuid); + if(serviceId != null) { + serviceIdList.add(serviceId); } } - return uuidList; + return serviceIdList; } - public static Set bannedMembersToUuidSet(Collection membersList) { - Set uuidSet = new HashSet<>(membersList.size()); + public static Set bannedMembersToServiceIdSet(Collection membersList) { + Set serviceIdSet = new HashSet<>(membersList.size()); for (DecryptedBannedMember member : membersList) { - UUID uuid = toUuid(member); - - if (!UuidUtil.UNKNOWN_UUID.equals(uuid)) { - uuidSet.add(uuid); + ServiceId serviceId = ServiceId.parseOrNull(member.getServiceIdBinary()); + if (serviceId != null) { + serviceIdSet.add(serviceId); } } - return uuidSet; + return serviceIdSet; } public static UUID toUuid(DecryptedMember member) { return toUuid(member.getUuid()); } - public static UUID toUuid(DecryptedPendingMember member) { - return toUuid(member.getUuid()); - } - - public static UUID toUuid(DecryptedBannedMember member) { - return toUuid(member.getUuid()); - } - private static UUID toUuid(ByteString memberUuid) { return UuidUtil.fromByteStringOrUnknown(memberUuid); } @@ -182,11 +181,11 @@ public static Optional findMemberByUuid(Collection findPendingByUuid(Collection members, UUID uuid) { - ByteString uuidBytes = UuidUtil.toByteString(uuid); + public static Optional findPendingByServiceId(Collection members, ServiceId serviceId) { + ByteString serviceIdBinary = serviceId.toByteString(); for (DecryptedPendingMember member : members) { - if (uuidBytes.equals(member.getUuid())) { + if (serviceIdBinary.equals(member.getServiceIdBinary())) { return Optional.of(member); } } @@ -196,7 +195,7 @@ public static Optional findPendingByUuid(Collection findPendingByServiceIds(Collection members, ServiceIds serviceIds) { for (DecryptedPendingMember member : members) { - if (serviceIds.matches(member.getUuid())) { + if (serviceIds.matches(member.getServiceIdBinary())) { return Optional.of(member); } } @@ -215,10 +214,10 @@ private static int findPendingIndexByUuidCipherText(List return -1; } - private static int findPendingIndexByUuid(List members, ByteString uuid) { + private static int findPendingIndexByServiceId(List members, ByteString serviceIdBinary) { for (int i = 0; i < members.size(); i++) { DecryptedPendingMember member = members.get(i); - if (uuid.equals(member.getUuid())) { + if (serviceIdBinary.equals(member.getServiceIdBinary())) { return i; } } @@ -416,7 +415,7 @@ private static void applyAddPendingMemberActions(DecryptedGroup.Builder builder, Set pendingMemberCipherTexts = getPendingMemberCipherTextSet(builder.getPendingMembersList()); for (DecryptedPendingMember pendingMember : newPendingMembersList) { - if (fullMemberSet.contains(pendingMember.getUuid())) { + if (fullMemberSet.contains(pendingMember.getServiceIdBinary())) { throw new NotAbleToApplyGroupV2ChangeException(); } @@ -441,7 +440,7 @@ protected static void applyDeletePendingMemberActions(DecryptedGroup.Builder bui protected static void applyPromotePendingMemberActions(DecryptedGroup.Builder builder, List promotePendingMembersList) throws NotAbleToApplyGroupV2ChangeException { for (DecryptedMember newMember : promotePendingMembersList) { - int index = findPendingIndexByUuid(builder.getPendingMembersList(), newMember.getUuid()); + int index = findPendingIndexByServiceId(builder.getPendingMembersList(), newMember.getUuid()); if (index == -1) { throw new NotAbleToApplyGroupV2ChangeException(); @@ -556,10 +555,10 @@ private static void applyInviteLinkPassword(DecryptedGroup.Builder builder, Decr } private static void applyAddBannedMembersActions(DecryptedGroup.Builder builder, List newBannedMembersList) { - Set bannedMemberUuidSet = getBannedMemberUuidSet(builder.getBannedMembersList()); + Set bannedMemberServiceIdSet = getBannedMemberServiceIdSet(builder.getBannedMembersList()); for (DecryptedBannedMember member : newBannedMembersList) { - if (bannedMemberUuidSet.contains(member.getUuid())) { + if (bannedMemberServiceIdSet.contains(member.getServiceIdBinary())) { Log.w(TAG, "Banned member already in banned list"); } else { builder.addBannedMembers(member); @@ -569,7 +568,7 @@ private static void applyAddBannedMembersActions(DecryptedGroup.Builder builder, private static void applyDeleteBannedMembersActions(DecryptedGroup.Builder builder, List deleteMembersList) { for (DecryptedBannedMember removedMember : deleteMembersList) { - int index = indexOfUuidInBannedMemberList(builder.getBannedMembersList(), removedMember.getUuid()); + int index = indexOfServiceIdInBannedMemberList(builder.getBannedMembersList(), removedMember.getServiceIdBinary()); if (index == -1) { Log.w(TAG, "Deleted banned member on change not found in banned list"); @@ -582,7 +581,7 @@ private static void applyDeleteBannedMembersActions(DecryptedGroup.Builder build protected static void applyPromotePendingPniAciMemberActions(DecryptedGroup.Builder builder, List promotePendingPniAciMembersList) throws NotAbleToApplyGroupV2ChangeException { for (DecryptedMember newMember : promotePendingPniAciMembersList) { - int index = findPendingIndexByUuid(builder.getPendingMembersList(), newMember.getPni()); + int index = findPendingIndexByServiceId(builder.getPendingMembersList(), newMember.getPni()); if (index == -1) { throw new NotAbleToApplyGroupV2ChangeException(); @@ -619,14 +618,14 @@ private static Set getPendingMemberCipherTextSet(List getBannedMemberUuidSet(List bannedMemberList) { - Set memberUuids = new HashSet<>(bannedMemberList.size()); + private static Set getBannedMemberServiceIdSet(List bannedMemberList) { + Set memberServiceIds = new HashSet<>(bannedMemberList.size()); for (DecryptedBannedMember member : bannedMemberList) { - memberUuids.add(member.getUuid()); + memberServiceIds.add(member.getServiceIdBinary()); } - return memberUuids; + return memberServiceIds; } private static void removePendingAndRequestingMembersNowInGroup(DecryptedGroup.Builder builder) { @@ -634,7 +633,7 @@ private static void removePendingAndRequestingMembersNowInGroup(DecryptedGroup.B for (int i = builder.getPendingMembersCount() - 1; i >= 0; i--) { DecryptedPendingMember pendingMember = builder.getPendingMembers(i); - if (allMembers.contains(pendingMember.getUuid())) { + if (allMembers.contains(pendingMember.getServiceIdBinary())) { builder.removePendingMembers(i); } } @@ -667,19 +666,13 @@ private static int indexOfUuidInRequestingList(List m return -1; } - private static int indexOfUuidInBannedMemberList(List memberList, ByteString uuid) { + private static int indexOfServiceIdInBannedMemberList(List memberList, ByteString serviceIdBinary) { for (int i = 0; i < memberList.size(); i++) { - if (uuid.equals(memberList.get(i).getUuid())) return i; + if (serviceIdBinary.equals(memberList.get(i).getServiceIdBinary())) return i; } return -1; } - public static Optional findInviter(List pendingMembersList, UUID uuid) { - return Optional.ofNullable(findPendingByUuid(pendingMembersList, uuid).map(DecryptedPendingMember::getAddedByUuid) - .map(UuidUtil::fromByteStringOrNull) - .orElse(null)); - } - public static boolean changeIsEmpty(DecryptedGroupChange change) { return change.getModifiedProfileKeysCount() == 0 && // field 6 changeIsEmptyExceptForProfileKeyChanges(change); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java index f326f81fc0..af1e94975e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java @@ -1,12 +1,12 @@ package org.whispersystems.signalservice.api.groupsv2; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil; import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.UUID; /** * Represents a potential new member of a group. @@ -15,20 +15,20 @@ *

* If it does not, then this user can only be invited. *

- * Equality by UUID only used to makes sure Sets only contain one copy. + * Equality by ServiceId only used to makes sure Sets only contain one copy. */ public final class GroupCandidate { - private final UUID uuid; + private final ServiceId serviceId; private final Optional expiringProfileKeyCredential; - public GroupCandidate(UUID uuid, Optional expiringProfileKeyCredential) { - this.uuid = uuid; + public GroupCandidate(ServiceId serviceId, Optional expiringProfileKeyCredential) { + this.serviceId = serviceId; this.expiringProfileKeyCredential = expiringProfileKeyCredential; } - public UUID getUuid() { - return uuid; + public ServiceId getServiceId() { + return serviceId; } public Optional getExpiringProfileKeyCredential() { @@ -57,12 +57,12 @@ public static Set withoutExpiringProfileKeyCredentials(Set fromStateMemberUuids = membersToSetOfUuids(fromState.getMembersList()); Set toStateMemberUuids = membersToSetOfUuids(toState.getMembersList()); - Set pendingMembersListA = pendingMembersToSetOfUuids(fromState.getPendingMembersList()); - Set pendingMembersListB = pendingMembersToSetOfUuids(toState.getPendingMembersList()); + Set pendingMembersListA = pendingMembersToSetOfServiceIds(fromState.getPendingMembersList()); + Set pendingMembersListB = pendingMembersToSetOfServiceIds(toState.getPendingMembersList()); Set requestingMembersListA = requestingMembersToSetOfUuids(fromState.getRequestingMembersList()); Set requestingMembersListB = requestingMembersToSetOfUuids(toState.getRequestingMembersList()); - Set bannedMembersListA = bannedMembersToSetOfUuids(fromState.getBannedMembersList()); - Set bannedMembersListB = bannedMembersToSetOfUuids(toState.getBannedMembersList()); + Set bannedMembersListA = bannedMembersToSetOfServiceIds(fromState.getBannedMembersList()); + Set bannedMembersListB = bannedMembersToSetOfServiceIds(toState.getBannedMembersList()); - Set removedPendingMemberUuids = subtract(pendingMembersListA, pendingMembersListB); - Set removedRequestingMemberUuids = subtract(requestingMembersListA, requestingMembersListB); - Set newPendingMemberUuids = subtract(pendingMembersListB, pendingMembersListA); - Set newRequestingMemberUuids = subtract(requestingMembersListB, requestingMembersListA); - Set removedMemberUuids = subtract(fromStateMemberUuids, toStateMemberUuids); - Set newMemberUuids = subtract(toStateMemberUuids, fromStateMemberUuids); - Set removedBannedMemberUuids = subtract(bannedMembersListA, bannedMembersListB); - Set newBannedMemberUuids = subtract(bannedMembersListB, bannedMembersListA); + Set removedPendingMemberServiceIds = subtract(pendingMembersListA, pendingMembersListB); + Set removedRequestingMemberUuids = subtract(requestingMembersListA, requestingMembersListB); + Set newPendingMemberServiceIds = subtract(pendingMembersListB, pendingMembersListA); + Set newRequestingMemberUuids = subtract(requestingMembersListB, requestingMembersListA); + Set removedMemberUuids = subtract(fromStateMemberUuids, toStateMemberUuids); + Set newMemberUuids = subtract(toStateMemberUuids, fromStateMemberUuids); + Set removedBannedMemberServiceIds = subtract(bannedMembersListA, bannedMembersListB); + Set newBannedMemberServiceIds = subtract(bannedMembersListB, bannedMembersListA); - Set addedByInvitationUuids = intersect(newMemberUuids, removedPendingMemberUuids); + Set addedByInvitationUuids = intersect(newMemberUuids, removedPendingMemberServiceIds); Set addedByRequestApprovalUuids = intersect(newMemberUuids, removedRequestingMemberUuids); Set addedMembersByInvitation = intersectByUUID(toState.getMembersList(), addedByInvitationUuids); Set addedMembersByRequestApproval = intersectByUUID(toState.getMembersList(), addedByRequestApprovalUuids); Set addedMembers = intersectByUUID(toState.getMembersList(), subtract(newMemberUuids, addedByInvitationUuids, addedByRequestApprovalUuids)); - Set uninvitedMembers = intersectPendingByUUID(fromState.getPendingMembersList(), subtract(removedPendingMemberUuids, addedByInvitationUuids)); + Set uninvitedMembers = intersectPendingByServiceId(fromState.getPendingMembersList(), subtract(removedPendingMemberServiceIds, addedByInvitationUuids)); Set rejectedRequestMembers = intersectRequestingByUUID(fromState.getRequestingMembersList(), subtract(removedRequestingMemberUuids, addedByRequestApprovalUuids)); for (DecryptedMember member : intersectByUUID(fromState.getMembersList(), removedMemberUuids)) { @@ -100,18 +100,18 @@ public static DecryptedGroupChange reconstructGroupChange(DecryptedGroup fromSta for (DecryptedPendingMember uninvitedMember : uninvitedMembers) { builder.addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() - .setUuid(uninvitedMember.getUuid()) + .setServiceIdBinary(uninvitedMember.getServiceIdBinary()) .setUuidCipherText(uninvitedMember.getUuidCipherText())); } - for (DecryptedPendingMember invitedMember : intersectPendingByUUID(toState.getPendingMembersList(), newPendingMemberUuids)) { + for (DecryptedPendingMember invitedMember : intersectPendingByServiceId(toState.getPendingMembersList(), newPendingMemberServiceIds)) { builder.addNewPendingMembers(invitedMember); } - Set consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids); - Set changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids); - Map membersUuidMap = uuidMap(fromState.getMembersList()); - Map bannedMembersUuidMap = bannedUuidMap(toState.getBannedMembersList()); + Set consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids); + Set changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids); + Map membersUuidMap = uuidMap(fromState.getMembersList()); + Map bannedMembersServiceIdMap = bannedServiceIdMap(toState.getBannedMembersList()); for (DecryptedMember newState : changedMembers) { DecryptedMember oldState = membersUuidMap.get(newState.getUuid()); @@ -148,13 +148,13 @@ public static DecryptedGroupChange reconstructGroupChange(DecryptedGroup fromSta builder.setNewInviteLinkPassword(toState.getInviteLinkPassword()); } - for (ByteString uuid : removedBannedMemberUuids) { - builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(uuid).build()); + for (ByteString serviceIdBinary : removedBannedMemberServiceIds) { + builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setServiceIdBinary(serviceIdBinary).build()); } - for (ByteString uuid : newBannedMemberUuids) { - DecryptedBannedMember.Builder newBannedBuilder = DecryptedBannedMember.newBuilder().setUuid(uuid); - DecryptedBannedMember bannedMember = bannedMembersUuidMap.get(uuid); + for (ByteString serviceIdBinary : newBannedMemberServiceIds) { + DecryptedBannedMember.Builder newBannedBuilder = DecryptedBannedMember.newBuilder().setServiceIdBinary(serviceIdBinary); + DecryptedBannedMember bannedMember = bannedMembersServiceIdMap.get(serviceIdBinary); if (bannedMember != null) { newBannedBuilder.setTimestamp(bannedMember.getTimestamp()); } @@ -173,10 +173,10 @@ private static Map uuidMap(List me return map; } - private static Map bannedUuidMap(List membersList) { + private static Map bannedServiceIdMap(List membersList) { Map map = new LinkedHashMap<>(membersList.size()); for (DecryptedBannedMember member : membersList) { - map.put(member.getUuid(), member); + map.put(member.getServiceIdBinary(), member); } return map; } @@ -190,10 +190,10 @@ private static Set intersectByUUID(Collection return result; } - private static Set intersectPendingByUUID(Collection members, Set uuids) { + private static Set intersectPendingByServiceId(Collection members, Set serviceIds) { Set result = new LinkedHashSet<>(members.size()); for (DecryptedPendingMember member : members) { - if (uuids.contains(member.getUuid())) + if (serviceIds.contains(member.getServiceIdBinary())) result.add(member); } return result; @@ -208,12 +208,12 @@ private static Set intersectRequestingByUUID(Collecti return result; } - private static Set pendingMembersToSetOfUuids(Collection pendingMembers) { - Set uuids = new LinkedHashSet<>(pendingMembers.size()); + private static Set pendingMembersToSetOfServiceIds(Collection pendingMembers) { + Set serviceIds = new LinkedHashSet<>(pendingMembers.size()); for (DecryptedPendingMember pendingMember : pendingMembers) { - uuids.add(pendingMember.getUuid()); + serviceIds.add(pendingMember.getServiceIdBinary()); } - return uuids; + return serviceIds; } private static Set requestingMembersToSetOfUuids(Collection requestingMembers) { @@ -232,12 +232,12 @@ private static Set membersToSetOfUuids(Collection m return uuids; } - private static Set bannedMembersToSetOfUuids(Collection bannedMembers) { - Set uuids = new LinkedHashSet<>(bannedMembers.size()); + private static Set bannedMembersToSetOfServiceIds(Collection bannedMembers) { + Set serviceIds = new LinkedHashSet<>(bannedMembers.size()); for (DecryptedBannedMember bannedMember : bannedMembers) { - uuids.add(bannedMember.getUuid()); + serviceIds.add(bannedMember.getServiceIdBinary()); } - return uuids; + return serviceIds; } private static Set subtract(Collection a, Collection b) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeUtil.java index 814a957a06..23e338f86e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeUtil.java @@ -107,17 +107,17 @@ private static void resolveConflict(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier changeSetModifier) { - HashMap fullMembersByUuid = new HashMap<>(groupState.getMembersCount()); - HashMap pendingMembersByUuid = new HashMap<>(groupState.getPendingMembersCount()); - HashMap requestingMembersByUuid = new HashMap<>(groupState.getMembersCount()); - HashMap bannedMembersByUuid = new HashMap<>(groupState.getBannedMembersCount()); + HashMap fullMembersByUuid = new HashMap<>(groupState.getMembersCount()); + HashMap pendingMembersByServiceId = new HashMap<>(groupState.getPendingMembersCount()); + HashMap requestingMembersByUuid = new HashMap<>(groupState.getMembersCount()); + HashMap bannedMembersByServiceId = new HashMap<>(groupState.getBannedMembersCount()); for (DecryptedMember member : groupState.getMembersList()) { fullMembersByUuid.put(member.getUuid(), member); } for (DecryptedPendingMember member : groupState.getPendingMembersList()) { - pendingMembersByUuid.put(member.getUuid(), member); + pendingMembersByServiceId.put(member.getServiceIdBinary(), member); } for (DecryptedRequestingMember member : groupState.getRequestingMembersList()) { @@ -125,33 +125,33 @@ private static void resolveConflict(DecryptedGroup groupState, } for (DecryptedBannedMember member : groupState.getBannedMembersList()) { - bannedMembersByUuid.put(member.getUuid(), member); + bannedMembersByServiceId.put(member.getServiceIdBinary(), member); } - resolveField3AddMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid); + resolveField3AddMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByServiceId); resolveField4DeleteMembers (conflictingChange, changeSetModifier, fullMembersByUuid); resolveField5ModifyMemberRoles (conflictingChange, changeSetModifier, fullMembersByUuid); resolveField6ModifyProfileKeys (conflictingChange, changeSetModifier, fullMembersByUuid); - resolveField7AddPendingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid); - resolveField8DeletePendingMembers (conflictingChange, changeSetModifier, pendingMembersByUuid); - resolveField9PromotePendingMembers (conflictingChange, changeSetModifier, pendingMembersByUuid); + resolveField7AddPendingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByServiceId); + resolveField8DeletePendingMembers (conflictingChange, changeSetModifier, pendingMembersByServiceId); + resolveField9PromotePendingMembers (conflictingChange, changeSetModifier, pendingMembersByServiceId); resolveField10ModifyTitle (groupState, conflictingChange, changeSetModifier); resolveField11ModifyAvatar (groupState, conflictingChange, changeSetModifier); resolveField12modifyDisappearingMessagesTimer(groupState, conflictingChange, changeSetModifier); resolveField13modifyAttributesAccess (groupState, conflictingChange, changeSetModifier); resolveField14modifyAttributesAccess (groupState, conflictingChange, changeSetModifier); resolveField15modifyAddFromInviteLinkAccess (groupState, conflictingChange, changeSetModifier); - resolveField16AddRequestingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid); + resolveField16AddRequestingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByServiceId); resolveField17DeleteMembers (conflictingChange, changeSetModifier, requestingMembersByUuid); resolveField18PromoteRequestingMembers (conflictingChange, changeSetModifier, requestingMembersByUuid); resolveField20ModifyDescription (groupState, conflictingChange, changeSetModifier); resolveField21ModifyAnnouncementsOnly (groupState, conflictingChange, changeSetModifier); - resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid); - resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid); + resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByServiceId); + resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByServiceId); resolveField24PromotePendingPniAciMembers (conflictingChange, changeSetModifier, fullMembersByUuid); } - private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByUuid) { + private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByServiceId) { List newMembersList = conflictingChange.getNewMembersList(); for (int i = newMembersList.size() - 1; i >= 0; i--) { @@ -159,7 +159,7 @@ private static void resolveField3AddMembers(DecryptedGroupChange conflictingChan if (fullMembersByUuid.containsKey(member.getUuid())) { result.removeAddMembers(i); - } else if (pendingMembersByUuid.containsKey(member.getUuid())) { + } else if (pendingMembersByServiceId.containsKey(member.getUuid())) { result.moveAddToPromote(i); } } @@ -203,37 +203,37 @@ private static void resolveField6ModifyProfileKeys(DecryptedGroupChange conflict } } - private static void resolveField7AddPendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByUuid) { + private static void resolveField7AddPendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByServiceId) { List newPendingMembersList = conflictingChange.getNewPendingMembersList(); for (int i = newPendingMembersList.size() - 1; i >= 0; i--) { DecryptedPendingMember member = newPendingMembersList.get(i); - if (fullMembersByUuid.containsKey(member.getUuid()) || pendingMembersByUuid.containsKey(member.getUuid())) { + if (fullMembersByUuid.containsKey(member.getServiceIdBinary()) || pendingMembersByServiceId.containsKey(member.getServiceIdBinary())) { result.removeAddPendingMembers(i); } } } - private static void resolveField8DeletePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap pendingMembersByUuid) { + private static void resolveField8DeletePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap pendingMembersByServiceId) { List deletePendingMembersList = conflictingChange.getDeletePendingMembersList(); for (int i = deletePendingMembersList.size() - 1; i >= 0; i--) { DecryptedPendingMemberRemoval member = deletePendingMembersList.get(i); - if (!pendingMembersByUuid.containsKey(member.getUuid())) { + if (!pendingMembersByServiceId.containsKey(member.getServiceIdBinary())) { result.removeDeletePendingMembers(i); } } } - private static void resolveField9PromotePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap pendingMembersByUuid) { + private static void resolveField9PromotePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap pendingMembersByServiceId) { List promotePendingMembersList = conflictingChange.getPromotePendingMembersList(); for (int i = promotePendingMembersList.size() - 1; i >= 0; i--) { DecryptedMember member = promotePendingMembersList.get(i); - if (!pendingMembersByUuid.containsKey(member.getUuid())) { + if (!pendingMembersByServiceId.containsKey(member.getUuid())) { result.removePromotePendingMembers(i); } } @@ -275,7 +275,7 @@ private static void resolveField15modifyAddFromInviteLinkAccess(DecryptedGroup g } } - private static void resolveField16AddRequestingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByUuid) { + private static void resolveField16AddRequestingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap fullMembersByUuid, HashMap pendingMembersByServiceId) { List newMembersList = conflictingChange.getNewRequestingMembersList(); for (int i = newMembersList.size() - 1; i >= 0; i--) { @@ -283,8 +283,8 @@ private static void resolveField16AddRequestingMembers(DecryptedGroupChange conf if (fullMembersByUuid.containsKey(member.getUuid())) { result.removeAddRequestingMembers(i); - } else if (pendingMembersByUuid.containsKey(member.getUuid())) { - result.moveAddRequestingMembersToPromote(i); + } else if (pendingMembersByServiceId.containsKey(member.getUuid())) { + result.moveAddRequestingMembersToPromote(i); } } } @@ -331,25 +331,25 @@ private static void resolveField21ModifyAnnouncementsOnly(DecryptedGroup groupSt } } - private static void resolveField22AddBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap bannedMembersByUuid) { + private static void resolveField22AddBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap bannedMembersByServiceId) { List newBannedMembersList = conflictingChange.getNewBannedMembersList(); for (int i = newBannedMembersList.size() - 1; i >= 0; i--) { DecryptedBannedMember member = newBannedMembersList.get(i); - if (bannedMembersByUuid.containsKey(member.getUuid())) { + if (bannedMembersByServiceId.containsKey(member.getServiceIdBinary())) { result.removeAddBannedMembers(i); } } } - private static void resolveField23DeleteBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap bannedMembersByUuid) { + private static void resolveField23DeleteBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap bannedMembersByServiceId) { List deleteBannedMembersList = conflictingChange.getDeleteBannedMembersList(); for (int i = deleteBannedMembersList.size() - 1; i >= 0; i--) { DecryptedBannedMember member = deleteBannedMembersList.get(i); - if (!bannedMembersByUuid.containsKey(member.getUuid())) { + if (!bannedMembersByServiceId.containsKey(member.getServiceIdBinary())) { result.removeDeleteBannedMembers(i); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java index 506bb2472a..ecbd372483 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java @@ -21,7 +21,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; -import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.exceptions.ForbiddenException; @@ -55,15 +56,15 @@ public CredentialResponseMaps getCredentials(long todaySeconds) /** * Create an auth token from a credential response. */ - public GroupsV2AuthorizationString getGroupsV2AuthorizationString(ServiceId aci, - ServiceId pni, + public GroupsV2AuthorizationString getGroupsV2AuthorizationString(ACI aci, + PNI pni, long redemptionTimeSeconds, GroupSecretParams groupSecretParams, AuthCredentialWithPniResponse authCredentialWithPniResponse) throws VerificationFailedException { ClientZkAuthOperations authOperations = groupsOperations.getAuthOperations(); - AuthCredentialWithPni authCredentialWithPni = authOperations.receiveAuthCredentialWithPni(aci.getRawUuid(), pni.getRawUuid(), redemptionTimeSeconds, authCredentialWithPniResponse); + AuthCredentialWithPni authCredentialWithPni = authOperations.receiveAuthCredentialWithPniAsServiceId(aci.getLibSignalAci(), pni.getLibSignalPni(), redemptionTimeSeconds, authCredentialWithPniResponse); AuthCredentialPresentation authCredentialPresentation = authOperations.createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredentialWithPni); return new GroupsV2AuthorizationString(groupSecretParams, authCredentialPresentation); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java index e1988b45ac..44ed973ba7 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java @@ -40,6 +40,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.EnabledState; import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -119,7 +120,7 @@ public NewGroup createNewGroup(final GroupSecretParams groupSecretParams, if (expiringProfileKeyCredential != null) { group.addMembers(groupOperations.member(expiringProfileKeyCredential, memberRole)); } else { - group.addPendingMembers(groupOperations.invitee(credential.getUuid(), memberRole)); + group.addPendingMembers(groupOperations.invitee(credential.getServiceId(), memberRole)); } } @@ -165,13 +166,13 @@ public GroupChange.Actions.Builder createModifyGroupDescription(final String des return GroupChange.Actions.newBuilder().setModifyDescription(createModifyGroupDescriptionAction(description)); } - public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set membersToAdd, Set bannedMembers, UUID selfUuid) { + public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set membersToAdd, Set bannedMembers, ACI selfAci) { final GroupOperations groupOperations = forGroup(groupSecretParams); - Set membersToUnban = membersToAdd.stream().map(GroupCandidate::getUuid).filter(bannedMembers::contains).collect(Collectors.toSet()); + Set membersToUnban = membersToAdd.stream().map(GroupCandidate::getServiceId).filter(bannedMembers::contains).collect(Collectors.toSet()); GroupChange.Actions.Builder actions = membersToUnban.isEmpty() ? GroupChange.Actions.newBuilder() - : createUnbanUuidsChange(membersToUnban); + : createUnbanServiceIdsChange(membersToUnban); for (GroupCandidate credential : membersToAdd) { Member.Role newMemberRole = Member.Role.DEFAULT; @@ -184,8 +185,8 @@ public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set reques for (UUID uuid : requestsToRemove) { actions.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction .newBuilder() - .setDeletedUserId(encryptUuid(uuid))); + .setDeletedUserId(encryptServiceId(ACI.from(uuid)))); } return actions; @@ -234,7 +235,7 @@ public GroupChange.Actions.Builder createApproveGroupJoinRequest(Set reque actions.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction .newBuilder() .setRole(Member.Role.DEFAULT) - .setUserId(encryptUuid(uuid))); + .setUserId(encryptServiceId(ACI.from(uuid)))); } return actions; @@ -247,7 +248,7 @@ public GroupChange.Actions.Builder createRemoveMembersChange(final Set mem for (UUID remove: membersToRemove) { actions.addDeleteMembers(GroupChange.Actions.DeleteMemberAction .newBuilder() - .setDeletedUserId(encryptUuid(remove))); + .setDeletedUserId(encryptServiceId(ACI.from(remove)))); } return actions; @@ -259,7 +260,7 @@ public GroupChange.Actions.Builder createLeaveAndPromoteMembersToAdmin(UUID self for (UUID member : membersToMakeAdmin) { actions.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction .newBuilder() - .setUserId(encryptUuid(member)) + .setUserId(encryptServiceId(ACI.from(member))) .setRole(Member.Role.ADMINISTRATOR)); } @@ -361,6 +362,7 @@ public GroupChange.Actions.Builder createAnnouncementGroupChange(boolean isAnnou .setAnnouncementsOnly(isAnnouncementGroup)); } + /** Note that this can only ban ACIs. */ public GroupChange.Actions.Builder createBanUuidsChange(Set banUuids, boolean rejectJoinRequest, List bannedMembersList) { GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false, Collections.emptyList()) : GroupChange.Actions.newBuilder(); @@ -370,26 +372,26 @@ public GroupChange.Actions.Builder createBanUuidsChange(Set banUuids, bool List unban = bannedMembersList.stream() .sorted(Comparator.comparingLong(DecryptedBannedMember::getTimestamp)) .limit(spacesToFree) - .map(DecryptedBannedMember::getUuid) + .map(DecryptedBannedMember::getServiceIdBinary) .collect(Collectors.toList()); - for (ByteString uuid : unban) { - builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptUuid(UuidUtil.fromByteString(uuid)))); + for (ByteString serviceIdBinary : unban) { + builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptServiceId(ServiceId.parseOrThrow(serviceIdBinary.toByteArray())))); } } for (UUID uuid : banUuids) { - builder.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encryptUuid(uuid)).build())); + builder.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encryptServiceId(ACI.from(uuid))).build())); } return builder; } - public GroupChange.Actions.Builder createUnbanUuidsChange(Set banUuids) { + public GroupChange.Actions.Builder createUnbanServiceIdsChange(Set serviceIds) { GroupChange.Actions.Builder builder = GroupChange.Actions.newBuilder(); - for (UUID uuid : banUuids) { - builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptUuid(uuid)).build()); + for (ServiceId serviceId : serviceIds) { + builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptServiceId(serviceId)).build()); } return builder; @@ -433,8 +435,8 @@ private RequestingMember.Builder requestingMember(ExpiringProfileKeyCredential c .setPresentation(ByteString.copyFrom(presentation.serialize())); } - public PendingMember.Builder invitee(UUID uuid, Member.Role role) { - UuidCiphertext uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid); + public PendingMember.Builder invitee(ServiceId serviceId, Member.Role role) { + UuidCiphertext uuidCiphertext = clientZkGroupCipher.encrypt(serviceId.getLibSignalServiceId()); Member member = Member.newBuilder() .setRole(role) @@ -454,17 +456,17 @@ public PartialDecryptedGroup partialDecryptGroup(Group group) List decryptedPendingMembers = new ArrayList<>(pendingMembersList.size()); for (Member member : membersList) { - UUID memberUuid = decryptUuid(member.getUserId()); + ACI memberAci = decryptAci(member.getUserId()); decryptedMembers.add(DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(memberUuid)) + .setUuid(memberAci.toByteString()) .setJoinedAtRevision(member.getJoinedAtRevision()) .build()); } for (PendingMember member : pendingMembersList) { - UUID pendingMemberUuid = decryptUuidOrUnknown(member.getMember().getUserId()); + ServiceId pendingMemberServiceId = decryptServiceIdOrUnknown(member.getMember().getUserId()); decryptedPendingMembers.add(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(pendingMemberUuid)) + .setServiceIdBinary(pendingMemberServiceId.toByteString()) .build()); } @@ -505,7 +507,7 @@ public DecryptedGroup decryptGroup(Group group) } for (BannedMember member : group.getBannedMembersList()) { - decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).setTimestamp(member.getTimestamp()).build()); + decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setServiceIdBinary(decryptServiceIdToBinary(member.getUserId())).setTimestamp(member.getTimestamp()).build()); } return DecryptedGroup.newBuilder() @@ -551,16 +553,16 @@ public DecryptedGroupChange decryptChange(GroupChange.Actions actions) return decryptChange(actions, null); } - public DecryptedGroupChange decryptChange(GroupChange.Actions actions, UUID source) + public DecryptedGroupChange decryptChange(GroupChange.Actions actions, ServiceId source) throws VerificationFailedException, InvalidGroupStateException { DecryptedGroupChange.Builder builder = DecryptedGroupChange.newBuilder(); // Field 1 if (source != null) { - builder.setEditor(UuidUtil.toByteString(source)); + builder.setEditor(source.toByteString()); } else { - builder.setEditor(decryptUuidToByteString(actions.getSourceUuid())); + builder.setEditor(decryptServiceIdToBinary(actions.getSourceUuid())); } // Field 2 @@ -577,35 +579,35 @@ public DecryptedGroupChange decryptChange(GroupChange.Actions actions, UUID sour // Field 4 for (GroupChange.Actions.DeleteMemberAction deleteMemberAction : actions.getDeleteMembersList()) { - builder.addDeleteMembers(decryptUuidToByteString(deleteMemberAction.getDeletedUserId())); + builder.addDeleteMembers(decryptServiceIdToBinary(deleteMemberAction.getDeletedUserId())); } // Field 5 for (GroupChange.Actions.ModifyMemberRoleAction modifyMemberRoleAction : actions.getModifyMemberRolesList()) { builder.addModifyMemberRoles(DecryptedModifyMemberRole.newBuilder() .setRole(modifyMemberRoleAction.getRole()) - .setUuid(decryptUuidToByteString(modifyMemberRoleAction.getUserId()))); + .setUuid(decryptServiceIdToBinary(modifyMemberRoleAction.getUserId()))); } // Field 6 for (GroupChange.Actions.ModifyMemberProfileKeyAction modifyMemberProfileKeyAction : actions.getModifyMemberProfileKeysList()) { try { - UUID uuid; + ACI aci; ProfileKey profileKey; if (modifyMemberProfileKeyAction.getUserId().isEmpty() || modifyMemberProfileKeyAction.getProfileKey().isEmpty()) { ProfileKeyCredentialPresentation presentation = new ProfileKeyCredentialPresentation(modifyMemberProfileKeyAction.getPresentation().toByteArray()); - uuid = decryptUuid(ByteString.copyFrom(presentation.getUuidCiphertext().serialize())); - profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), uuid); + aci = decryptAci(ByteString.copyFrom(presentation.getUuidCiphertext().serialize())); + profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), aci); } else { - uuid = decryptUuid(modifyMemberProfileKeyAction.getUserId()); - profileKey = decryptProfileKey(modifyMemberProfileKeyAction.getProfileKey(), uuid); + aci = decryptAci(modifyMemberProfileKeyAction.getUserId()); + profileKey = decryptProfileKey(modifyMemberProfileKeyAction.getProfileKey(), aci); } builder.addModifiedProfileKeys(DecryptedMember.newBuilder() .setRole(Member.Role.UNKNOWN) .setJoinedAtRevision(-1) - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(aci.toByteString()) .setProfileKey(ByteString.copyFrom(profileKey.serialize()))); } catch (InvalidInputException e) { throw new InvalidGroupStateException(e); @@ -617,45 +619,45 @@ public DecryptedGroupChange decryptChange(GroupChange.Actions actions, UUID sour PendingMember added = addPendingMemberAction.getAdded(); Member member = added.getMember(); ByteString uuidCipherText = member.getUserId(); - UUID uuid = decryptUuidOrUnknown(uuidCipherText); + ServiceId serviceId = decryptServiceIdOrUnknown(uuidCipherText); builder.addNewPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(serviceId.toByteString()) .setUuidCipherText(uuidCipherText) .setRole(member.getRole()) - .setAddedByUuid(decryptUuidToByteString(added.getAddedByUserId())) + .setAddedByUuid(decryptServiceIdToBinary(added.getAddedByUserId())) .setTimestamp(added.getTimestamp())); } // Field 8 for (GroupChange.Actions.DeletePendingMemberAction deletePendingMemberAction : actions.getDeletePendingMembersList()) { ByteString uuidCipherText = deletePendingMemberAction.getDeletedUserId(); - UUID uuid = decryptUuidOrUnknown(uuidCipherText); + ServiceId serviceId = decryptServiceIdOrUnknown(uuidCipherText); builder.addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(serviceId.toByteString()) .setUuidCipherText(uuidCipherText)); } // Field 9 for (GroupChange.Actions.PromotePendingMemberAction promotePendingMemberAction : actions.getPromotePendingMembersList()) { try { - UUID uuid; + ACI aci; ProfileKey profileKey; if (promotePendingMemberAction.getUserId().isEmpty() || promotePendingMemberAction.getProfileKey().isEmpty()) { ProfileKeyCredentialPresentation presentation = new ProfileKeyCredentialPresentation(promotePendingMemberAction.getPresentation().toByteArray()); - uuid = decryptUuid(ByteString.copyFrom(presentation.getUuidCiphertext().serialize())); - profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), uuid); + aci = decryptAci(ByteString.copyFrom(presentation.getUuidCiphertext().serialize())); + profileKey = decryptProfileKey(ByteString.copyFrom(presentation.getProfileKeyCiphertext().serialize()), aci); } else { - uuid = decryptUuid(promotePendingMemberAction.getUserId()); - profileKey = decryptProfileKey(promotePendingMemberAction.getProfileKey(), uuid); + aci = decryptAci(promotePendingMemberAction.getUserId()); + profileKey = decryptProfileKey(promotePendingMemberAction.getProfileKey(), aci); } builder.addPromotePendingMembers(DecryptedMember.newBuilder() .setJoinedAtRevision(-1) .setRole(Member.Role.DEFAULT) - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(aci.toByteString()) .setProfileKey(ByteString.copyFrom(profileKey.serialize()))); } catch (InvalidInputException e) { throw new InvalidGroupStateException(e); @@ -700,12 +702,12 @@ public DecryptedGroupChange decryptChange(GroupChange.Actions actions, UUID sour // Field 17 for (GroupChange.Actions.DeleteRequestingMemberAction delete : actions.getDeleteRequestingMembersList()) { - builder.addDeleteRequestingMembers(decryptUuidToByteString(delete.getDeletedUserId())); + builder.addDeleteRequestingMembers(decryptServiceIdToBinary(delete.getDeletedUserId())); } // Field 18 for (GroupChange.Actions.PromoteRequestingMemberAction promote : actions.getPromoteRequestingMembersList()) { - builder.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder().setRole(promote.getRole()).setUuid(decryptUuidToByteString(promote.getUserId()))); + builder.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder().setRole(promote.getRole()).setUuid(decryptServiceIdToBinary(promote.getUserId()))); } // Field 19 @@ -725,27 +727,31 @@ public DecryptedGroupChange decryptChange(GroupChange.Actions actions, UUID sour // Field 22 for (GroupChange.Actions.AddBannedMemberAction action : actions.getAddBannedMembersList()) { - builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).setTimestamp(action.getAdded().getTimestamp()).build()); + builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setServiceIdBinary(decryptServiceIdToBinary(action.getAdded().getUserId())).setTimestamp(action.getAdded().getTimestamp()).build()); } // Field 23 for (GroupChange.Actions.DeleteBannedMemberAction action : actions.getDeleteBannedMembersList()) { - builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getDeletedUserId())).build()); + builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setServiceIdBinary(decryptServiceIdToBinary(action.getDeletedUserId())).build()); } // Field 24 for (GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction promotePendingPniAciMemberAction : actions.getPromotePendingPniAciMembersList()) { - UUID uuid = decryptUuid(promotePendingPniAciMemberAction.getUserId()); - UUID pni = decryptUuid(promotePendingPniAciMemberAction.getPni()); - ProfileKey profileKey = decryptProfileKey(promotePendingPniAciMemberAction.getProfileKey(), uuid); + ACI aci = decryptAci(promotePendingPniAciMemberAction.getUserId()); + ServiceId pni = decryptServiceId(promotePendingPniAciMemberAction.getPni()); + ProfileKey profileKey = decryptProfileKey(promotePendingPniAciMemberAction.getProfileKey(), aci); + + if (!(pni instanceof PNI)) { + throw new InvalidGroupStateException(); + } - builder.setEditor(UuidUtil.toByteString(uuid)) + builder.setEditor(aci.toByteString()) .addPromotePendingPniAciMembers(DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(aci.toByteString()) .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(actions.getRevision()) - .setPni(UuidUtil.toByteString(pni))); + .setPni(pni.toByteString())); } return builder.build(); @@ -767,20 +773,26 @@ private DecryptedMember.Builder decryptMember(Member member) throws InvalidGroupStateException, VerificationFailedException, InvalidInputException { if (member.getPresentation().isEmpty()) { - UUID uuid = decryptUuid(member.getUserId()); + ACI aci = decryptAci(member.getUserId()); return DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(aci.toByteString()) .setJoinedAtRevision(member.getJoinedAtRevision()) - .setProfileKey(decryptProfileKeyToByteString(member.getProfileKey(), uuid)) + .setProfileKey(decryptProfileKeyToByteString(member.getProfileKey(), aci)) .setRole(member.getRole()); } else { ProfileKeyCredentialPresentation profileKeyCredentialPresentation = new ProfileKeyCredentialPresentation(member.getPresentation().toByteArray()); - UUID uuid = clientZkGroupCipher.decryptUuid(profileKeyCredentialPresentation.getUuidCiphertext()); - ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), uuid); + + ServiceId serviceId = ServiceId.fromLibSignal(clientZkGroupCipher.decrypt(profileKeyCredentialPresentation.getUuidCiphertext())); + if (!(serviceId instanceof ACI)) { + throw new InvalidGroupStateException(); + } + ACI serviceIdAsAci = (ACI)serviceId; + + ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), serviceIdAsAci.getLibSignalAci()); return DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(serviceIdAsAci.toByteString()) .setJoinedAtRevision(member.getJoinedAtRevision()) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setRole(member.getRole()); @@ -791,8 +803,8 @@ private DecryptedPendingMember decryptMember(PendingMember member) throws InvalidGroupStateException, VerificationFailedException { ByteString userIdCipherText = member.getMember().getUserId(); - UUID uuid = decryptUuidOrUnknown(userIdCipherText); - UUID addedBy = decryptUuid(member.getAddedByUserId()); + ServiceId serviceId = decryptServiceIdOrUnknown(userIdCipherText); + ACI addedBy = decryptAci(member.getAddedByUserId()); Member.Role role = member.getMember().getRole(); @@ -801,9 +813,9 @@ private DecryptedPendingMember decryptMember(PendingMember member) } return DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(serviceId.toByteString()) .setUuidCipherText(userIdCipherText) - .setAddedByUuid(UuidUtil.toByteString(addedBy)) + .setAddedByUuid(addedBy.toByteString()) .setRole(role) .setTimestamp(member.getTimestamp()) .build(); @@ -813,11 +825,11 @@ private DecryptedRequestingMember decryptRequestingMember(RequestingMember membe throws InvalidGroupStateException, VerificationFailedException { if (member.getPresentation().isEmpty()) { - UUID uuid = decryptUuid(member.getUserId()); + ACI aci = decryptAci(member.getUserId()); return DecryptedRequestingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) - .setProfileKey(decryptProfileKeyToByteString(member.getProfileKey(), uuid)) + .setUuid(aci.toByteString()) + .setProfileKey(decryptProfileKeyToByteString(member.getProfileKey(), aci)) .setTimestamp(member.getTimestamp()) .build(); } else { @@ -828,54 +840,67 @@ private DecryptedRequestingMember decryptRequestingMember(RequestingMember membe throw new InvalidGroupStateException(e); } - UUID uuid = clientZkGroupCipher.decryptUuid(profileKeyCredentialPresentation.getUuidCiphertext()); - ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), uuid); + ServiceId serviceId = ServiceId.fromLibSignal(clientZkGroupCipher.decrypt(profileKeyCredentialPresentation.getUuidCiphertext())); + if (!(serviceId instanceof ACI)) { + throw new InvalidGroupStateException(); + } + ACI serviceIdAsAci = (ACI)serviceId; + + ProfileKey profileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCredentialPresentation.getProfileKeyCiphertext(), serviceIdAsAci.getLibSignalAci()); return DecryptedRequestingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setUuid(serviceIdAsAci.toByteString()) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .build(); } } - private ProfileKey decryptProfileKey(ByteString profileKey, UUID uuid) throws VerificationFailedException, InvalidGroupStateException { + private ProfileKey decryptProfileKey(ByteString profileKey, ACI aci) throws VerificationFailedException, InvalidGroupStateException { try { ProfileKeyCiphertext profileKeyCiphertext = new ProfileKeyCiphertext(profileKey.toByteArray()); - return clientZkGroupCipher.decryptProfileKey(profileKeyCiphertext, uuid); + return clientZkGroupCipher.decryptProfileKey(profileKeyCiphertext, aci.getLibSignalAci()); } catch (InvalidInputException e) { throw new InvalidGroupStateException(e); } } - private ByteString decryptProfileKeyToByteString(ByteString profileKey, UUID uuid) throws VerificationFailedException, InvalidGroupStateException { - return ByteString.copyFrom(decryptProfileKey(profileKey, uuid).serialize()); + private ByteString decryptProfileKeyToByteString(ByteString profileKey, ACI aci) throws VerificationFailedException, InvalidGroupStateException { + return ByteString.copyFrom(decryptProfileKey(profileKey, aci).serialize()); } - private ByteString decryptUuidToByteString(ByteString userId) throws InvalidGroupStateException, VerificationFailedException { - return UuidUtil.toByteString(decryptUuid(userId)); + private ByteString decryptServiceIdToBinary(ByteString userId) throws InvalidGroupStateException, VerificationFailedException { + return decryptServiceId(userId).toByteString(); } // Visible for Testing - public ByteString encryptUuid(UUID uuid) { - return ByteString.copyFrom(clientZkGroupCipher.encryptUuid(uuid).serialize()); + public ByteString encryptServiceId(ServiceId serviceId) { + return ByteString.copyFrom(clientZkGroupCipher.encrypt(serviceId.getLibSignalServiceId()).serialize()); } - private UUID decryptUuid(ByteString userId) throws InvalidGroupStateException, VerificationFailedException { + private ServiceId decryptServiceId(ByteString userId) throws InvalidGroupStateException, VerificationFailedException { try { - return clientZkGroupCipher.decryptUuid(new UuidCiphertext(userId.toByteArray())); + return ServiceId.fromLibSignal(clientZkGroupCipher.decrypt(new UuidCiphertext(userId.toByteArray()))); } catch (InvalidInputException e) { throw new InvalidGroupStateException(e); } } + private ACI decryptAci(ByteString userId) throws InvalidGroupStateException, VerificationFailedException { + ServiceId result = decryptServiceId(userId); + if (result instanceof ACI) { + return (ACI)result; + } + throw new InvalidGroupStateException(); + } + /** - * Attempts to decrypt a UUID, but will return {@link #UNKNOWN_UUID} if it cannot. + * Attempts to decrypt a UUID, but will return an ACI of {@link #UNKNOWN_UUID} if it cannot. */ - private UUID decryptUuidOrUnknown(ByteString userId) { + private ServiceId decryptServiceIdOrUnknown(ByteString userId) { try { - return clientZkGroupCipher.decryptUuid(new UuidCiphertext(userId.toByteArray())); + return ServiceId.fromLibSignal(clientZkGroupCipher.decrypt(new UuidCiphertext(userId.toByteArray()))); } catch (InvalidInputException | VerificationFailedException e) { - return UNKNOWN_UUID; + return ACI.UNKNOWN; } } @@ -977,10 +1002,10 @@ private GroupChange.Actions getActions(GroupChange groupChange) return GroupChange.Actions.parseFrom(groupChange.getActions()); } - public GroupChange.Actions.Builder createChangeMemberRole(UUID uuid, Member.Role role) { + public GroupChange.Actions.Builder createChangeMemberRole(ACI memberAci, Member.Role role) { return GroupChange.Actions.newBuilder() .addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder() - .setUserId(encryptUuid(uuid)) + .setUserId(encryptServiceId(memberAci)) .setRole(role)); } @@ -990,7 +1015,7 @@ public List decryptAddMembers(List ACI(serviceId) + is LibSignalPni -> PNI(serviceId) + else -> throw IllegalArgumentException("Unknown libsignal ServiceId type!") + } + } + /** Parses a ServiceId serialized as a string. Returns null if the ServiceId is invalid. */ @JvmStatic fun parseOrNull(raw: String?): ServiceId? { @@ -29,11 +38,7 @@ sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServ } return try { - when (val serviceId = LibSignalServiceId.parseFromString(raw)) { - is LibSignalAci -> ACI(serviceId) - is LibSignalPni -> PNI(serviceId) - else -> null - } + fromLibSignal(LibSignalServiceId.parseFromString(raw)) } catch (e: IllegalArgumentException) { null } catch (e: InvalidServiceIdException) { @@ -49,11 +54,7 @@ sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServ } return try { - return when (val serviceId = LibSignalServiceId.parseFromBinary(raw)) { - is LibSignalAci -> ACI.from(serviceId.rawUUID) - is LibSignalPni -> PNI.from(serviceId.rawUUID) - else -> null - } + fromLibSignal(LibSignalServiceId.parseFromBinary(raw)) } catch (e: IllegalArgumentException) { null } catch (e: InvalidServiceIdException) { @@ -61,6 +62,10 @@ sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServ } } + /** Parses a ServiceId serialized as a ByteString. Returns null if the ServiceId is invalid. */ + @JvmStatic + fun parseOrNull(bytes: ByteString): ServiceId? = parseOrNull(bytes.toByteArray()) + /** Parses a ServiceId serialized as a string. Crashes if the ServiceId is invalid. */ @JvmStatic @Throws(IllegalArgumentException::class) @@ -77,27 +82,27 @@ sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServ fun parseOrThrow(bytes: ByteString): ServiceId = parseOrThrow(bytes.toByteArray()) } - val rawUuid: UUID = libsignalServiceId.rawUUID + val rawUuid: UUID = libSignalServiceId.rawUUID val isUnknown: Boolean = rawUuid == UuidUtil.UNKNOWN_UUID val isValid: Boolean = !isUnknown - fun toProtocolAddress(deviceId: Int): SignalProtocolAddress = SignalProtocolAddress(libsignalServiceId.toServiceIdString(), deviceId) + fun toProtocolAddress(deviceId: Int): SignalProtocolAddress = SignalProtocolAddress(libSignalServiceId.toServiceIdString(), deviceId) - fun toByteString(): ByteString = ByteString.copyFrom(libsignalServiceId.toServiceIdBinary()) + fun toByteString(): ByteString = ByteString.copyFrom(libSignalServiceId.toServiceIdBinary()) - fun toByteArray(): ByteArray = libsignalServiceId.toServiceIdBinary() + fun toByteArray(): ByteArray = libSignalServiceId.toServiceIdBinary() - fun logString(): String = libsignalServiceId.toLogString() + fun logString(): String = libSignalServiceId.toLogString() /** * A serialized string that can be parsed via [parseOrThrow], for instance. * Basically ACI's are just normal UUIDs, and PNI's are UUIDs with a `PNI:` prefix. */ - override fun toString(): String = libsignalServiceId.toServiceIdString() + override fun toString(): String = libSignalServiceId.toServiceIdString() - data class ACI(val libsignalAci: LibSignalAci) : ServiceId(libsignalAci) { + data class ACI(val libSignalAci: LibSignalAci) : ServiceId(libSignalAci) { companion object { @JvmField val UNKNOWN = from(UuidUtil.UNKNOWN_UUID) @@ -133,7 +138,7 @@ sealed class ServiceId(@JvmField protected val libsignalServiceId: LibSignalServ override fun toString(): String = super.toString() } - data class PNI(private val libsignalPni: LibSignalPni) : ServiceId(libsignalPni) { + data class PNI(val libSignalPni: LibSignalPni) : ServiceId(libSignalPni) { companion object { @JvmField var UNKNOWN = from(UuidUtil.UNKNOWN_UUID) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index 550d4cc00e..8043613647 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -1,6 +1,7 @@ package org.whispersystems.signalservice.api.services; import org.signal.libsignal.protocol.IdentityKey; +import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; @@ -15,6 +16,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException; import org.whispersystems.signalservice.internal.ServiceResponse; @@ -81,11 +83,17 @@ public Single> getProfile(@Nonnull SignalS .setVerb("GET"); if (profileKey.isPresent()) { - ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(serviceId.getRawUuid()); + if (!(serviceId instanceof ACI)) { + Log.w(TAG, "ServiceId must be an ACI if a profile key is available!"); + return Single.just(ServiceResponse.forUnknownError(new IllegalArgumentException("ServiceId must be an ACI if a profile key is available!"))); + } + + ACI aci = (ACI) serviceId; + ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(aci.getLibSignalAci()); String version = profileKeyIdentifier.serialize(); if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { - requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, serviceId.getRawUuid(), profileKey.get()); + requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, aci.getLibSignalAci(), profileKey.get()); ProfileKeyCredentialRequest request = requestContext.getRequest(); String credentialRequest = Hex.toStringCondensed(request.serialize()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 5a33de0359..01a5b34fe5 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -258,7 +258,7 @@ public class PushServiceSocket { private static final String STICKER_MANIFEST_PATH = "stickers/%s/manifest.proto"; private static final String STICKER_PATH = "stickers/%s/full/%d"; - private static final String GROUPSV2_CREDENTIAL = "/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d"; + private static final String GROUPSV2_CREDENTIAL = "/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d&pniAsServiceId=true"; private static final String GROUPSV2_GROUP = "/v1/groups/"; private static final String GROUPSV2_GROUP_PASSWORD = "/v1/groups/?inviteLinkPassword=%s"; private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s?maxSupportedChangeEpoch=%d&includeFirstState=%s&includeLastState=false"; @@ -853,9 +853,9 @@ public ListenableFuture retrieveProfile(SignalServiceAddre }); } - public ListenableFuture retrieveVersionedProfileAndCredential(UUID target, ProfileKey profileKey, Optional unidentifiedAccess, Locale locale) { - ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target); - ProfileKeyCredentialRequestContext requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, target, profileKey); + public ListenableFuture retrieveVersionedProfileAndCredential(ACI target, ProfileKey profileKey, Optional unidentifiedAccess, Locale locale) { + ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target.getLibSignalAci()); + ProfileKeyCredentialRequestContext requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, target.getLibSignalAci(), profileKey); ProfileKeyCredentialRequest request = requestContext.getRequest(); String version = profileKeyIdentifier.serialize(); @@ -889,8 +889,8 @@ private ProfileAndCredential formatProfileAndCredentialBody(ProfileKeyCredential } } - public ListenableFuture retrieveVersionedProfile(UUID target, ProfileKey profileKey, Optional unidentifiedAccess, Locale locale) { - ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target); + public ListenableFuture retrieveVersionedProfile(ACI target, ProfileKey profileKey, Optional unidentifiedAccess, Locale locale) { + ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target.getLibSignalAci()); String version = profileKeyIdentifier.serialize(); String subPath = String.format("%s/%s", target, version); diff --git a/libsignal/service/src/main/proto/DecryptedGroups.proto b/libsignal/service/src/main/proto/DecryptedGroups.proto index 45e6cde52b..235e133373 100644 --- a/libsignal/service/src/main/proto/DecryptedGroups.proto +++ b/libsignal/service/src/main/proto/DecryptedGroups.proto @@ -21,11 +21,11 @@ message DecryptedMember { } message DecryptedPendingMember { - bytes uuid = 1; - Member.Role role = 2; - bytes addedByUuid = 3; - uint64 timestamp = 4; - bytes uuidCipherText = 5; + bytes serviceIdBinary = 1; + Member.Role role = 2; + bytes addedByUuid = 3; + uint64 timestamp = 4; + bytes uuidCipherText = 5; } message DecryptedRequestingMember { @@ -35,13 +35,13 @@ message DecryptedRequestingMember { } message DecryptedBannedMember { - bytes uuid = 1; - uint64 timestamp = 2; + bytes serviceIdBinary = 1; + uint64 timestamp = 2; } message DecryptedPendingMemberRemoval { - bytes uuid = 1; - bytes uuidCipherText = 2; + bytes serviceIdBinary = 1; + bytes uuidCipherText = 2; } message DecryptedApproveMember { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtilTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtilTest.java index 7cd09c5f17..f66d311f3e 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtilTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtilTest.java @@ -7,6 +7,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval; +import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.util.Util; @@ -15,6 +17,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static java.util.Arrays.asList; @@ -58,39 +61,39 @@ public void can_extract_editor_uuid_from_decrypted_group_change() { @Test public void can_extract_uuid_from_decrypted_pending_member() { - UUID uuid = UUID.randomUUID(); + ACI aci = ACI.from(UUID.randomUUID()); DecryptedPendingMember decryptedMember = DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(aci.toByteString()) .build(); - UUID parsed = DecryptedGroupUtil.toUuid(decryptedMember); + ServiceId parsed = ServiceId.parseOrNull(decryptedMember.getServiceIdBinary()); - assertEquals(uuid, parsed); + assertEquals(aci, parsed); } @Test public void can_extract_uuid_from_bad_decrypted_pending_member() { DecryptedPendingMember decryptedMember = DecryptedPendingMember.newBuilder() - .setUuid(ByteString.copyFrom(Util.getSecretBytes(17))) + .setServiceIdBinary(ByteString.copyFrom(Util.getSecretBytes(18))) .build(); - UUID parsed = DecryptedGroupUtil.toUuid(decryptedMember); + ServiceId parsed = ServiceId.parseOrNull(decryptedMember.getServiceIdBinary()); - assertEquals(UuidUtil.UNKNOWN_UUID, parsed); + assertNull(parsed); } @Test public void can_extract_uuids_for_all_pending_including_bad_entries() { - UUID uuid1 = UUID.randomUUID(); - UUID uuid2 = UUID.randomUUID(); + ACI aci1 = ACI.from(UUID.randomUUID()); + ACI aci2 = ACI.from(UUID.randomUUID()); DecryptedPendingMember decryptedMember1 = DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid1)) + .setServiceIdBinary(aci1.toByteString()) .build(); DecryptedPendingMember decryptedMember2 = DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid2)) + .setServiceIdBinary(aci2.toByteString()) .build(); DecryptedPendingMember decryptedMember3 = DecryptedPendingMember.newBuilder() - .setUuid(ByteString.copyFrom(Util.getSecretBytes(17))) + .setServiceIdBinary(ByteString.copyFrom(Util.getSecretBytes(18))) .build(); DecryptedGroupChange groupChange = DecryptedGroupChange.newBuilder() @@ -99,23 +102,23 @@ public void can_extract_uuids_for_all_pending_including_bad_entries() { .addNewPendingMembers(decryptedMember3) .build(); - List pendingUuids = DecryptedGroupUtil.pendingToUuidList(groupChange.getNewPendingMembersList()); + List pendingUuids = DecryptedGroupUtil.pendingToServiceIdList(groupChange.getNewPendingMembersList()); - assertThat(pendingUuids, is(asList(uuid1, uuid2, UuidUtil.UNKNOWN_UUID))); + assertThat(pendingUuids, is(asList(aci1, aci2, ACI.UNKNOWN))); } @Test public void can_extract_uuids_for_all_deleted_pending_excluding_bad_entries() { - UUID uuid1 = UUID.randomUUID(); - UUID uuid2 = UUID.randomUUID(); + ACI aci1 = ACI.from(UUID.randomUUID()); + ACI aci2 = ACI.from(UUID.randomUUID()); DecryptedPendingMemberRemoval decryptedMember1 = DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(uuid1)) + .setServiceIdBinary(aci1.toByteString()) .build(); DecryptedPendingMemberRemoval decryptedMember2 = DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(uuid2)) + .setServiceIdBinary(aci2.toByteString()) .build(); DecryptedPendingMemberRemoval decryptedMember3 = DecryptedPendingMemberRemoval.newBuilder() - .setUuid(ByteString.copyFrom(Util.getSecretBytes(17))) + .setServiceIdBinary(ByteString.copyFrom(Util.getSecretBytes(18))) .build(); DecryptedGroupChange groupChange = DecryptedGroupChange.newBuilder() @@ -124,23 +127,23 @@ public void can_extract_uuids_for_all_deleted_pending_excluding_bad_entries() { .addDeletePendingMembers(decryptedMember3) .build(); - List removedUuids = DecryptedGroupUtil.removedPendingMembersUuidList(groupChange); + List removedUuids = DecryptedGroupUtil.removedPendingMembersServiceIdList(groupChange); - assertThat(removedUuids, is(asList(uuid1, uuid2))); + assertThat(removedUuids, is(asList(aci1, aci2))); } @Test public void can_extract_uuids_for_all_deleted_members_excluding_bad_entries() { - UUID uuid1 = UUID.randomUUID(); - UUID uuid2 = UUID.randomUUID(); + ACI aci1 = ACI.from(UUID.randomUUID()); + ACI aci2 = ACI.from(UUID.randomUUID()); DecryptedGroupChange groupChange = DecryptedGroupChange.newBuilder() - .addDeleteMembers(UuidUtil.toByteString(uuid1)) - .addDeleteMembers(UuidUtil.toByteString(uuid2)) - .addDeleteMembers(ByteString.copyFrom(Util.getSecretBytes(17))) + .addDeleteMembers(aci1.toByteString()) + .addDeleteMembers(aci2.toByteString()) + .addDeleteMembers(ByteString.copyFrom(Util.getSecretBytes(18))) .build(); - List removedUuids = DecryptedGroupUtil.removedMembersUuidList(groupChange); + List removedServiceIds = DecryptedGroupUtil.removedMembersServiceIdList(groupChange); - assertThat(removedUuids, is(asList(uuid1, uuid2))); + assertThat(removedServiceIds, is(asList(aci1, aci2))); } } diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil_apply_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil_apply_Test.java index 18c8c79866..36c6562c24 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil_apply_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil_apply_Test.java @@ -949,7 +949,7 @@ public void remove_banned_member() throws NotAbleToApplyGroupV2ChangeException { DecryptedGroupChange.newBuilder() .setRevision(11) .addDeleteBannedMembers(DecryptedBannedMember.newBuilder() - .setUuid(UuidUtil.toByteString(bannedUuid)) + .setServiceIdBinary(UuidUtil.toByteString(bannedUuid)) .build()) .build()); diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java index 5deeba25f5..7514fa01ef 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java @@ -12,6 +12,8 @@ import org.signal.storageservice.protos.groups.GroupChange.Actions.AddBannedMemberAction; import org.signal.storageservice.protos.groups.GroupChange.Actions.DeleteBannedMemberAction; import org.signal.storageservice.protos.groups.local.DecryptedBannedMember; +import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; @@ -52,7 +54,7 @@ public void addBanToEmptyList() { Collections.emptyList()); assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); - assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(ban))); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptServiceId(ACI.from(ban)))); } @Test @@ -69,7 +71,7 @@ public void addBanToPartialFullList() { alreadyBanned); assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); - assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan))); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptServiceId(ACI.from(toBan)))); } @Test @@ -93,11 +95,11 @@ public void addBanToFullList() { alreadyBanned); assertThat(banUuidsChange.getDeleteBannedMembersCount(), is(1)); - assertThat(banUuidsChange.getDeleteBannedMembers(0).getDeletedUserId(), is(groupOperations.encryptUuid(UuidUtil.fromByteString(oldest.getUuid())))); + assertThat(banUuidsChange.getDeleteBannedMembers(0).getDeletedUserId(), is(groupOperations.encryptServiceId(ServiceId.parseOrThrow(oldest.getServiceIdBinary())))); assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); - assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan))); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptServiceId(ACI.from(toBan)))); } @Test @@ -112,8 +114,8 @@ public void addMultipleBanToFullList() { } List oldest = new ArrayList<>(2); - oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(0).getUuid()))); - oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(1).getUuid()))); + oldest.add(groupOperations.encryptServiceId(ServiceId.parseOrThrow(alreadyBanned.get(0).getServiceIdBinary()))); + oldest.add(groupOperations.encryptServiceId(ServiceId.parseOrThrow(alreadyBanned.get(1).getServiceIdBinary()))); Collections.shuffle(alreadyBanned); @@ -135,6 +137,7 @@ public void addMultipleBanToFullList() { .map(AddBannedMemberAction::getAdded) .map(BannedMember::getUserId) .collect(Collectors.toList()), - hasItems(groupOperations.encryptUuid(toBan.get(0)), groupOperations.encryptUuid(toBan.get(1)))); + hasItems(groupOperations.encryptServiceId(ACI.from(toBan.get(0))), + groupOperations.encryptServiceId(ACI.from(toBan.get(1))))); } } diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java index 8398586dde..258696f705 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java @@ -33,6 +33,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedString; import org.signal.storageservice.protos.groups.local.DecryptedTimer; import org.signal.storageservice.protos.groups.local.EnabledState; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; @@ -94,8 +96,8 @@ public void can_pass_revision_through_encrypt_and_decrypt_methods() { @Test public void can_decrypt_member_additions_field3() { - UUID self = UUID.randomUUID(); - UUID newMember = UUID.randomUUID(); + ACI self = ACI.from(UUID.randomUUID()); + ACI newMember = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); @@ -107,12 +109,12 @@ public void can_decrypt_member_additions_field3() { .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(10) - .setUuid(UuidUtil.toByteString(newMember)))); + .setUuid(newMember.toByteString()))); } @Test public void can_decrypt_member_direct_join_field3() { - UUID newMember = UUID.randomUUID(); + ACI newMember = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); @@ -124,13 +126,13 @@ public void can_decrypt_member_direct_join_field3() { .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(10) - .setUuid(UuidUtil.toByteString(newMember)))); + .setUuid(newMember.toByteString()))); } @Test public void can_decrypt_member_additions_direct_to_admin_field3() { - UUID self = UUID.randomUUID(); - UUID newMember = UUID.randomUUID(); + ACI self = ACI.from(UUID.randomUUID()); + ACI newMember = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); @@ -142,7 +144,7 @@ public void can_decrypt_member_additions_direct_to_admin_field3() { .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(10) - .setUuid(UuidUtil.toByteString(newMember)))); + .setUuid(newMember.toByteString()))); } @Test(expected = InvalidGroupStateException.class) @@ -181,29 +183,29 @@ public void cannot_decrypt_member_removals_with_bad_cipher_text_field4() throws @Test public void can_decrypt_modify_member_action_role_to_admin_field5() { - UUID member = UUID.randomUUID(); + ACI member = ACI.from(UUID.randomUUID()); assertDecryption(groupOperations.createChangeMemberRole(member, Member.Role.ADMINISTRATOR), DecryptedGroupChange.newBuilder() .addModifyMemberRoles(DecryptedModifyMemberRole.newBuilder() - .setUuid(UuidUtil.toByteString(member)) + .setUuid(member.toByteString()) .setRole(Member.Role.ADMINISTRATOR))); } @Test public void can_decrypt_modify_member_action_role_to_member_field5() { - UUID member = UUID.randomUUID(); + ACI member = ACI.from(UUID.randomUUID()); assertDecryption(groupOperations.createChangeMemberRole(member, Member.Role.DEFAULT), DecryptedGroupChange.newBuilder() .addModifyMemberRoles(DecryptedModifyMemberRole.newBuilder() - .setUuid(UuidUtil.toByteString(member)) + .setUuid(member.toByteString()) .setRole(Member.Role.DEFAULT))); } @Test public void can_decrypt_modify_member_profile_key_action_field6() { - UUID self = UUID.randomUUID(); + ACI self = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(self, profileKey); @@ -215,35 +217,35 @@ public void can_decrypt_modify_member_profile_key_action_field6() { .setRole(Member.Role.UNKNOWN) .setJoinedAtRevision(-1) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) - .setUuid(UuidUtil.toByteString(self)))); + .setUuid(self.toByteString()))); } @Test public void can_decrypt_member_invitations_field7() { - UUID self = UUID.randomUUID(); - UUID newMember = UUID.randomUUID(); - GroupCandidate groupCandidate = groupCandidate(newMember); + ACI self = ACI.from(UUID.randomUUID()); + ACI newMember = ACI.from(UUID.randomUUID()); + GroupCandidate groupCandidate = new GroupCandidate(newMember, Optional.empty()); assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), Collections.emptySet(), self) .setRevision(13), DecryptedGroupChange.newBuilder() .setRevision(13) .addNewPendingMembers(DecryptedPendingMember.newBuilder() - .setAddedByUuid(UuidUtil.toByteString(self)) - .setUuidCipherText(groupOperations.encryptUuid(newMember)) + .setAddedByUuid(self.toByteString()) + .setUuidCipherText(groupOperations.encryptServiceId(newMember)) .setRole(Member.Role.DEFAULT) - .setUuid(UuidUtil.toByteString(newMember)))); + .setServiceIdBinary(newMember.toByteString()))); } @Test public void can_decrypt_pending_member_removals_field8() throws InvalidInputException { - UUID oldMember = UUID.randomUUID(); - UuidCiphertext uuidCiphertext = new UuidCiphertext(groupOperations.encryptUuid(oldMember).toByteArray()); + ACI oldMember = ACI.from(UUID.randomUUID()); + UuidCiphertext uuidCiphertext = new UuidCiphertext(groupOperations.encryptServiceId(oldMember).toByteArray()); assertDecryption(groupOperations.createRemoveInvitationChange(Collections.singleton(uuidCiphertext)), DecryptedGroupChange.newBuilder() .addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(oldMember)) + .setServiceIdBinary(oldMember.toByteString()) .setUuidCipherText(ByteString.copyFrom(uuidCiphertext.serialize())))); } @@ -257,20 +259,20 @@ public void can_decrypt_pending_member_removals_with_bad_cipher_text_field8() { .setDeletedUserId(ByteString.copyFrom(uuidCiphertext))), DecryptedGroupChange.newBuilder() .addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID)) + .setServiceIdBinary(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID)) .setUuidCipherText(ByteString.copyFrom(uuidCiphertext)))); } @Test public void can_decrypt_promote_pending_member_field9() { - UUID newMember = UUID.randomUUID(); + ACI newMember = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); assertDecryption(groupOperations.createAcceptInviteChange(groupCandidate.getExpiringProfileKeyCredential().get()), DecryptedGroupChange.newBuilder() .addPromotePendingMembers(DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(newMember)) + .setUuid(newMember.toByteString()) .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(-1))); @@ -328,7 +330,7 @@ public void can_pass_through_new_add_by_invite_link_rights_field_15_unsatisfiabl @Test public void can_decrypt_member_requests_field16() { - UUID newRequestingMember = UUID.randomUUID(); + ACI newRequestingMember = ACI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newRequestingMember, profileKey); @@ -337,7 +339,7 @@ public void can_decrypt_member_requests_field16() { DecryptedGroupChange.newBuilder() .setRevision(10) .addNewRequestingMembers(DecryptedRequestingMember.newBuilder() - .setUuid(UuidUtil.toByteString(newRequestingMember)) + .setUuid(newRequestingMember.toByteString()) .setProfileKey(ByteString.copyFrom(profileKey.serialize())))); } @@ -350,7 +352,7 @@ public void can_decrypt_member_requests_refusals_field17() { DecryptedGroupChange.newBuilder() .setRevision(10) .addDeleteRequestingMembers(UuidUtil.toByteString(newRequestingMember)) - .addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newRequestingMember)).build())); + .addNewBannedMembers(DecryptedBannedMember.newBuilder().setServiceIdBinary(UuidUtil.toByteString(newRequestingMember)).build())); } @Test @@ -402,42 +404,42 @@ public void can_decrypt_member_bans_field22() { DecryptedGroupChange.newBuilder() .setRevision(13) .addNewBannedMembers(DecryptedBannedMember.newBuilder() - .setUuid(UuidUtil.toByteString(ban)))); + .setServiceIdBinary(UuidUtil.toByteString(ban)))); } @Test public void can_decrypt_banned_member_removals_field23() { - UUID ban = UUID.randomUUID(); + ACI ban = ACI.from(UUID.randomUUID()); - assertDecryption(groupOperations.createUnbanUuidsChange(Collections.singleton(ban)) + assertDecryption(groupOperations.createUnbanServiceIdsChange(Collections.singleton(ban)) .setRevision(13), DecryptedGroupChange.newBuilder() .setRevision(13) .addDeleteBannedMembers(DecryptedBannedMember.newBuilder() - .setUuid(UuidUtil.toByteString(ban)))); + .setServiceIdBinary(ban.toByteString()))); } @Test public void can_decrypt_promote_pending_pni_aci_member_field24() { - UUID memberUuid = UUID.randomUUID(); - UUID memberPni = UUID.randomUUID(); + ACI memberAci = ACI.from(UUID.randomUUID()); + PNI memberPni = PNI.from(UUID.randomUUID()); ProfileKey profileKey = newProfileKey(); GroupChange.Actions.Builder builder = GroupChange.Actions.newBuilder() - .setSourceUuid(groupOperations.encryptUuid(memberPni)) + .setSourceUuid(groupOperations.encryptServiceId(memberPni)) .setRevision(5) .addPromotePendingPniAciMembers(GroupChange.Actions.PromotePendingPniAciMemberProfileKeyAction.newBuilder() - .setUserId(groupOperations.encryptUuid(memberUuid)) - .setPni(groupOperations.encryptUuid(memberPni)) - .setProfileKey(encryptProfileKey(memberUuid, profileKey))); + .setUserId(groupOperations.encryptServiceId(memberAci)) + .setPni(groupOperations.encryptServiceId(memberPni)) + .setProfileKey(encryptProfileKey(memberAci, profileKey))); assertDecryptionWithEditorSet(builder, DecryptedGroupChange.newBuilder() - .setEditor(UuidUtil.toByteString(memberUuid)) + .setEditor(memberAci.toByteString()) .setRevision(5) .addPromotePendingPniAciMembers(DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(memberUuid)) - .setPni(UuidUtil.toByteString(memberPni)) + .setUuid(memberAci.toByteString()) + .setPni(memberPni.toByteString()) .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(5))); @@ -451,23 +453,23 @@ private static ProfileKey newProfileKey() { } } - private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) { - return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize()); + private ByteString encryptProfileKey(ACI aci, ProfileKey profileKey) { + return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, aci.getLibSignalAci()).serialize()); } static GroupCandidate groupCandidate(UUID uuid) { - return new GroupCandidate(uuid, Optional.empty()); + return new GroupCandidate(ACI.from(uuid), Optional.empty()); } - GroupCandidate groupCandidate(UUID uuid, ProfileKey profileKey) { + GroupCandidate groupCandidate(ACI aci, ProfileKey profileKey) { try { ClientZkProfileOperations profileOperations = clientZkOperations.getProfileOperations(); - ProfileKeyCommitment commitment = profileKey.getCommitment(uuid); - ProfileKeyCredentialRequestContext requestContext = profileOperations.createProfileKeyCredentialRequestContext(uuid, profileKey); + ProfileKeyCommitment commitment = profileKey.getCommitment(aci.getLibSignalAci()); + ProfileKeyCredentialRequestContext requestContext = profileOperations.createProfileKeyCredentialRequestContext(aci.getLibSignalAci(), profileKey); ProfileKeyCredentialRequest request = requestContext.getRequest(); - ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = server.getExpiringProfileKeyCredentialResponse(request, uuid, commitment, Instant.now().plus(7, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS)); + ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = server.getExpiringProfileKeyCredentialResponse(request, aci, commitment, Instant.now().plus(7, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS)); ExpiringProfileKeyCredential profileKeyCredential = profileOperations.receiveExpiringProfileKeyCredential(requestContext, expiringProfileKeyCredentialResponse); - GroupCandidate groupCandidate = new GroupCandidate(uuid, Optional.of(profileKeyCredential)); + GroupCandidate groupCandidate = new GroupCandidate(aci, Optional.of(profileKeyCredential)); ProfileKeyCredentialPresentation presentation = profileOperations.createProfileKeyCredentialPresentation(groupSecretParams, profileKeyCredential); server.assertProfileKeyCredentialPresentation(groupSecretParams.getPublicParams(), presentation, Instant.now()); @@ -481,8 +483,8 @@ GroupCandidate groupCandidate(UUID uuid, ProfileKey profileKey) { void assertDecryption(GroupChange.Actions.Builder inputChange, DecryptedGroupChange.Builder expectedDecrypted) { - UUID editor = UUID.randomUUID(); - assertDecryptionWithEditorSet(inputChange.setSourceUuid(groupOperations.encryptUuid(editor)), expectedDecrypted.setEditor(UuidUtil.toByteString(editor))); + ACI editor = ACI.from(UUID.randomUUID()); + assertDecryptionWithEditorSet(inputChange.setSourceUuid(groupOperations.encryptServiceId(editor)), expectedDecrypted.setEditor(editor.toByteString())); } void assertDecryptionWithEditorSet(GroupChange.Actions.Builder inputChange, diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java index 769ee3fcee..cd9c3c87be 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java @@ -22,7 +22,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; import org.signal.storageservice.protos.groups.local.EnabledState; -import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; @@ -122,20 +122,20 @@ public void set_revision_field_6() throws VerificationFailedException, InvalidGr @Test public void decrypt_full_members_field_7() throws VerificationFailedException, InvalidGroupStateException { - UUID admin1 = UUID.randomUUID(); - UUID member1 = UUID.randomUUID(); + ACI admin1 = ACI.from(UUID.randomUUID()); + ACI member1 = ACI.from(UUID.randomUUID()); ProfileKey adminProfileKey = newProfileKey(); ProfileKey memberProfileKey = newProfileKey(); Group group = Group.newBuilder() .addMembers(Member.newBuilder() .setRole(Member.Role.ADMINISTRATOR) - .setUserId(groupOperations.encryptUuid(admin1)) + .setUserId(groupOperations.encryptServiceId(admin1)) .setJoinedAtRevision(4) .setProfileKey(encryptProfileKey(admin1, adminProfileKey))) .addMembers(Member.newBuilder() .setRole(Member.Role.DEFAULT) - .setUserId(groupOperations.encryptUuid(member1)) + .setUserId(groupOperations.encryptServiceId(member1)) .setJoinedAtRevision(7) .setProfileKey(encryptProfileKey(member1, memberProfileKey))) .build(); @@ -145,13 +145,13 @@ public void decrypt_full_members_field_7() throws VerificationFailedException, I assertEquals(DecryptedGroup.newBuilder() .addMembers(DecryptedMember.newBuilder() .setJoinedAtRevision(4) - .setUuid(UuidUtil.toByteString(admin1)) + .setUuid(admin1.toByteString()) .setRole(Member.Role.ADMINISTRATOR) .setProfileKey(ByteString.copyFrom(adminProfileKey.serialize()))) .addMembers(DecryptedMember.newBuilder() .setJoinedAtRevision(7) .setRole(Member.Role.DEFAULT) - .setUuid(UuidUtil.toByteString(member1)) + .setUuid(member1.toByteString()) .setProfileKey(ByteString.copyFrom(memberProfileKey.serialize()))) .build().getMembersList(), decryptedGroup.getMembersList()); @@ -159,52 +159,52 @@ public void decrypt_full_members_field_7() throws VerificationFailedException, I @Test public void decrypt_pending_members_field_8() throws VerificationFailedException, InvalidGroupStateException { - UUID admin1 = UUID.randomUUID(); - UUID member1 = UUID.randomUUID(); - UUID member2 = UUID.randomUUID(); - UUID inviter1 = UUID.randomUUID(); - UUID inviter2 = UUID.randomUUID(); + ACI admin1 = ACI.from(UUID.randomUUID()); + ACI member1 = ACI.from(UUID.randomUUID()); + ACI member2 = ACI.from(UUID.randomUUID()); + ACI inviter1 = ACI.from(UUID.randomUUID()); + ACI inviter2 = ACI.from(UUID.randomUUID()); Group group = Group.newBuilder() .addPendingMembers(PendingMember.newBuilder() - .setAddedByUserId(groupOperations.encryptUuid(inviter1)) + .setAddedByUserId(groupOperations.encryptServiceId(inviter1)) .setTimestamp(100) .setMember(Member.newBuilder() .setRole(Member.Role.ADMINISTRATOR) - .setUserId(groupOperations.encryptUuid(admin1)))) + .setUserId(groupOperations.encryptServiceId(admin1)))) .addPendingMembers(PendingMember.newBuilder() - .setAddedByUserId(groupOperations.encryptUuid(inviter1)) + .setAddedByUserId(groupOperations.encryptServiceId(inviter1)) .setTimestamp(200) .setMember(Member.newBuilder() .setRole(Member.Role.DEFAULT) - .setUserId(groupOperations.encryptUuid(member1)))) + .setUserId(groupOperations.encryptServiceId(member1)))) .addPendingMembers(PendingMember.newBuilder() - .setAddedByUserId(groupOperations.encryptUuid(inviter2)) + .setAddedByUserId(groupOperations.encryptServiceId(inviter2)) .setTimestamp(1500) .setMember(Member.newBuilder() - .setUserId(groupOperations.encryptUuid(member2)))) + .setUserId(groupOperations.encryptServiceId(member2)))) .build(); DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group); assertEquals(DecryptedGroup.newBuilder() .addPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(admin1)) - .setUuidCipherText(groupOperations.encryptUuid(admin1)) + .setServiceIdBinary(admin1.toByteString()) + .setUuidCipherText(groupOperations.encryptServiceId(admin1)) .setTimestamp(100) - .setAddedByUuid(UuidUtil.toByteString(inviter1)) + .setAddedByUuid(inviter1.toByteString()) .setRole(Member.Role.ADMINISTRATOR)) .addPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(member1)) - .setUuidCipherText(groupOperations.encryptUuid(member1)) + .setServiceIdBinary(member1.toByteString()) + .setUuidCipherText(groupOperations.encryptServiceId(member1)) .setTimestamp(200) - .setAddedByUuid(UuidUtil.toByteString(inviter1)) + .setAddedByUuid(inviter1.toByteString()) .setRole(Member.Role.DEFAULT)) .addPendingMembers(DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(member2)) - .setUuidCipherText(groupOperations.encryptUuid(member2)) + .setServiceIdBinary(member2.toByteString()) + .setUuidCipherText(groupOperations.encryptServiceId(member2)) .setTimestamp(1500) - .setAddedByUuid(UuidUtil.toByteString(inviter2)) + .setAddedByUuid(inviter2.toByteString()) .setRole(Member.Role.DEFAULT)) .build().getPendingMembersList(), decryptedGroup.getPendingMembersList()); @@ -212,18 +212,18 @@ public void decrypt_pending_members_field_8() throws VerificationFailedException @Test public void decrypt_requesting_members_field_9() throws VerificationFailedException, InvalidGroupStateException { - UUID admin1 = UUID.randomUUID(); - UUID member1 = UUID.randomUUID(); + ACI admin1 = ACI.from(UUID.randomUUID()); + ACI member1 = ACI.from(UUID.randomUUID()); ProfileKey adminProfileKey = newProfileKey(); ProfileKey memberProfileKey = newProfileKey(); Group group = Group.newBuilder() .addRequestingMembers(RequestingMember.newBuilder() - .setUserId(groupOperations.encryptUuid(admin1)) + .setUserId(groupOperations.encryptServiceId(admin1)) .setProfileKey(encryptProfileKey(admin1, adminProfileKey)) .setTimestamp(5000)) .addRequestingMembers(RequestingMember.newBuilder() - .setUserId(groupOperations.encryptUuid(member1)) + .setUserId(groupOperations.encryptServiceId(member1)) .setProfileKey(encryptProfileKey(member1, memberProfileKey)) .setTimestamp(15000)) .build(); @@ -232,11 +232,11 @@ public void decrypt_requesting_members_field_9() throws VerificationFailedExcept assertEquals(DecryptedGroup.newBuilder() .addRequestingMembers(DecryptedRequestingMember.newBuilder() - .setUuid(UuidUtil.toByteString(admin1)) + .setUuid(admin1.toByteString()) .setProfileKey(ByteString.copyFrom(adminProfileKey.serialize())) .setTimestamp(5000)) .addRequestingMembers(DecryptedRequestingMember.newBuilder() - .setUuid(UuidUtil.toByteString(member1)) + .setUuid(member1.toByteString()) .setProfileKey(ByteString.copyFrom(memberProfileKey.serialize())) .setTimestamp(15000)) .build().getRequestingMembersList(), @@ -279,20 +279,20 @@ public void decrypt_announcements_field_12() throws VerificationFailedException, @Test public void decrypt_banned_members_field_13() throws VerificationFailedException, InvalidGroupStateException { - UUID member1 = UUID.randomUUID(); + ACI member1 = ACI.from(UUID.randomUUID()); Group group = Group.newBuilder() - .addBannedMembers(BannedMember.newBuilder().setUserId(groupOperations.encryptUuid(member1))) + .addBannedMembers(BannedMember.newBuilder().setUserId(groupOperations.encryptServiceId(member1))) .build(); DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group); assertEquals(1, decryptedGroup.getBannedMembersCount()); - assertEquals(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(member1)).build(), decryptedGroup.getBannedMembers(0)); + assertEquals(DecryptedBannedMember.newBuilder().setServiceIdBinary(member1.toByteString()).build(), decryptedGroup.getBannedMembers(0)); } - private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) { - return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize()); + private ByteString encryptProfileKey(ACI aci, ProfileKey profileKey) { + return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, aci.getLibSignalAci()).serialize()); } private static ProfileKey newProfileKey() { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/ProtoTestUtils.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/ProtoTestUtils.java index 7feece9c2c..d2b43cf9ef 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/ProtoTestUtils.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/ProtoTestUtils.java @@ -107,14 +107,14 @@ static DecryptedMember member(UUID uuid, ByteString profileKey, int joinedAtRevi static DecryptedPendingMemberRemoval pendingMemberRemoval(UUID uuid) { return DecryptedPendingMemberRemoval.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(UuidUtil.toByteString(uuid)) .setUuidCipherText(encrypt(uuid)) .build(); } static DecryptedPendingMember pendingMember(UUID uuid) { return DecryptedPendingMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(UuidUtil.toByteString(uuid)) .setUuidCipherText(encrypt(uuid)) .setRole(Member.Role.DEFAULT) .build(); @@ -133,7 +133,7 @@ static DecryptedRequestingMember requestingMember(UUID uuid, ProfileKey profileK static DecryptedBannedMember bannedMember(UUID uuid) { return DecryptedBannedMember.newBuilder() - .setUuid(UuidUtil.toByteString(uuid)) + .setServiceIdBinary(UuidUtil.toByteString(uuid)) .build(); } diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/TestZkGroupServer.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/TestZkGroupServer.java index a416a026b9..bd074e5a14 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/TestZkGroupServer.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/TestZkGroupServer.java @@ -9,10 +9,10 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialPresentation; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; +import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; import java.time.Instant; -import java.util.UUID; /** * Provides Zk group operations that the server would provide. @@ -35,8 +35,8 @@ public ServerPublicParams getServerPublicParams() { return serverPublicParams; } - public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse(ProfileKeyCredentialRequest request, UUID uuid, ProfileKeyCommitment commitment, Instant expiration) throws VerificationFailedException { - return serverZkProfileOperations.issueExpiringProfileKeyCredential(request, uuid, commitment, expiration); + public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse(ProfileKeyCredentialRequest request, ACI aci, ProfileKeyCommitment commitment, Instant expiration) throws VerificationFailedException { + return serverZkProfileOperations.issueExpiringProfileKeyCredential(request, aci.getLibSignalAci(), commitment, expiration); } public void assertProfileKeyCredentialPresentation(GroupPublicParams publicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation, Instant now) { From e7972d4903e6ce87459af8dcffb7bb04a1e8870a Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 31 Jul 2023 13:32:35 -0400 Subject: [PATCH 15/66] Update request and response properties for batch identity checks. --- .../contacts/paged/SafetyNumberRepository.kt | 14 ++++----- .../paged/SafetyNumberRepositoryTest.kt | 4 +-- .../api/services/ProfileService.java | 14 ++++----- .../internal/push/IdentityCheckRequest.java | 24 +++++++-------- .../internal/push/IdentityCheckResponse.java | 30 +++++++++---------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepository.kt index 7c77398946..a63bdca0d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepository.kt @@ -50,12 +50,12 @@ class SafetyNumberRepository( if (recipients.isNotEmpty()) { Log.i(TAG, "Checking on ${recipients.size} identities...") - val requests: List>> = recipients.chunked(batchSize) { it.createBatchRequestSingle() } + val requests: List>> = recipients.chunked(batchSize) { it.createBatchRequestSingle() } stopwatch.split("requests") - val aciKeyPairs: List = Single.zip(requests) { responses -> + val aciKeyPairs: List = Single.zip(requests) { responses -> responses - .map { it as List } + .map { it as List } .flatten() }.safeBlockingGet() @@ -65,8 +65,8 @@ class SafetyNumberRepository( Log.d(TAG, "No identity key mismatches") } else { aciKeyPairs - .filter { it.aci != null && it.identityKey != null } - .forEach { IdentityUtil.saveIdentity(it.aci.toString(), it.identityKey) } + .filter { it.serviceId != null && it.identityKey != null } + .forEach { IdentityUtil.saveIdentity(it.serviceId.toString(), it.identityKey) } } recentlyFetched += recipients.associate { it.id to now } stopwatch.split("saving-identities") @@ -95,7 +95,7 @@ class SafetyNumberRepository( .apply { remove(Recipient.self().id) } } - private fun List.createBatchRequestSingle(): Single> { + private fun List.createBatchRequestSingle(): Single> { return profileService .performIdentityCheck( mapNotNull { r -> @@ -107,7 +107,7 @@ class SafetyNumberRepository( } }.associate { it } ) - .map { ServiceResponseProcessor.DefaultProcessor(it).resultOrThrow.aciKeyPairs ?: emptyList() } + .map { ServiceResponseProcessor.DefaultProcessor(it).resultOrThrow.serviceIdKeyPairs ?: emptyList() } .onErrorReturn { t -> Log.w(TAG, "Unable to fetch identities", t) emptyList() diff --git a/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt index bf03432844..f0f3a220d8 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/contacts/paged/SafetyNumberRepositoryTest.kt @@ -130,7 +130,7 @@ class SafetyNumberRepositoryTest { staticRecipient.`when`> { Recipient.resolvedList(argThat { containsAll(keys.map { it.recipientId }) }) }.thenReturn(listOf(other)) whenever(profileService.performIdentityCheck(mapOf(other.requireServiceId() to identityPool[other]!!.identityKey))) - .thenReturn(Single.just(ServiceResponse.forResult(IdentityCheckResponse(listOf(IdentityCheckResponse.AciIdentityPair(otherAci, otherNewIdentityKey))), 200, ""))) + .thenReturn(Single.just(ServiceResponse.forResult(IdentityCheckResponse(listOf(IdentityCheckResponse.ServiceIdentityPair(otherAci, otherNewIdentityKey))), 200, ""))) repository.batchSafetyNumberCheckSync(keys, now) @@ -151,7 +151,7 @@ class SafetyNumberRepositoryTest { staticRecipient.`when`> { Recipient.resolvedList(argThat { containsAll(keys.map { it.recipientId }) }) }.thenReturn(listOf(other, secondOther)) whenever(profileService.performIdentityCheck(mapOf(other.requireServiceId() to identityPool[other]!!.identityKey, secondOther.requireServiceId() to identityPool[secondOther]!!.identityKey))) - .thenReturn(Single.just(ServiceResponse.forResult(IdentityCheckResponse(listOf(IdentityCheckResponse.AciIdentityPair(otherAci, otherNewIdentityKey))), 200, ""))) + .thenReturn(Single.just(ServiceResponse.forResult(IdentityCheckResponse(listOf(IdentityCheckResponse.ServiceIdentityPair(otherAci, otherNewIdentityKey))), 200, ""))) repository.batchSafetyNumberCheckSync(keys, now) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index 8043613647..5b30c5e04a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -22,7 +22,7 @@ import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.ServiceResponseProcessor; import org.whispersystems.signalservice.internal.push.IdentityCheckRequest; -import org.whispersystems.signalservice.internal.push.IdentityCheckRequest.AciFingerprintPair; +import org.whispersystems.signalservice.internal.push.IdentityCheckRequest.ServiceIdFingerprintPair; import org.whispersystems.signalservice.internal.push.IdentityCheckResponse; import org.whispersystems.signalservice.internal.push.http.AcceptLanguagesUtil; import org.whispersystems.signalservice.internal.util.Hex; @@ -120,13 +120,13 @@ public Single> getProfile(@Nonnull SignalS .onErrorReturn(ServiceResponse::forUnknownError); } - public @NonNull Single> performIdentityCheck(@Nonnull Map aciIdentityKeyMap) { - List aciKeyPairs = aciIdentityKeyMap.entrySet() - .stream() - .map(e -> new AciFingerprintPair(e.getKey(), e.getValue())) - .collect(Collectors.toList()); + public @NonNull Single> performIdentityCheck(@Nonnull Map serviceIdIdentityKeyMap) { + List serviceIdKeyPairs = serviceIdIdentityKeyMap.entrySet() + .stream() + .map(e -> new ServiceIdFingerprintPair(e.getKey(), e.getValue())) + .collect(Collectors.toList()); - IdentityCheckRequest request = new IdentityCheckRequest(aciKeyPairs); + IdentityCheckRequest request = new IdentityCheckRequest(serviceIdKeyPairs); WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder() .setId(new SecureRandom().nextLong()) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckRequest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckRequest.java index dd964ce9fe..cb57ddae90 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckRequest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckRequest.java @@ -16,27 +16,27 @@ public class IdentityCheckRequest { @JsonProperty("elements") - private final List aciFingerprintPairs; + private final List serviceIdFingerprintPairs; - public IdentityCheckRequest(@Nonnull List aciKeyPairs) { - this.aciFingerprintPairs = aciKeyPairs; + public IdentityCheckRequest(@Nonnull List serviceIdKeyPairs) { + this.serviceIdFingerprintPairs = serviceIdKeyPairs; } - public List getAciFingerprintPairs() { - return aciFingerprintPairs; + public List getServiceIdFingerprintPairs() { + return serviceIdFingerprintPairs; } - public static final class AciFingerprintPair { + public static final class ServiceIdFingerprintPair { - @JsonProperty + @JsonProperty("uuid") @JsonSerialize(using = JsonUtil.ServiceIdSerializer.class) - private final ServiceId aci; + private final ServiceId serviceId; @JsonProperty private final String fingerprint; - public AciFingerprintPair(@Nonnull ServiceId aci, @Nonnull IdentityKey identityKey) { - this.aci = aci; + public ServiceIdFingerprintPair(@Nonnull ServiceId serviceId, @Nonnull IdentityKey identityKey) { + this.serviceId = serviceId; try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); @@ -46,8 +46,8 @@ public AciFingerprintPair(@Nonnull ServiceId aci, @Nonnull IdentityKey identityK } } - public ServiceId getAci() { - return aci; + public ServiceId getServiceId() { + return serviceId; } public String getFingerprint() { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java index 252dce3f39..3e89cd82d3 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/IdentityCheckResponse.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.signal.libsignal.protocol.IdentityKey; -import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.internal.util.JsonUtil; import java.util.List; @@ -14,39 +14,39 @@ public class IdentityCheckResponse { @JsonProperty("elements") - private List aciKeyPairs; + private List serviceIdKeyPairs; public IdentityCheckResponse() {} // Visible for testing - public IdentityCheckResponse(List aciKeyPairs) { - this.aciKeyPairs = aciKeyPairs; + public IdentityCheckResponse(List serviceIdKeyPairs) { + this.serviceIdKeyPairs = serviceIdKeyPairs; } - public @Nullable List getAciKeyPairs() { - return aciKeyPairs; + public @Nullable List getServiceIdKeyPairs() { + return serviceIdKeyPairs; } - public static final class AciIdentityPair { + public static final class ServiceIdentityPair { - @JsonProperty - @JsonDeserialize(using = JsonUtil.AciDeserializer.class) - private ACI aci; + @JsonProperty("uuid") + @JsonDeserialize(using = JsonUtil.ServiceIdDeserializer.class) + private ServiceId serviceId; @JsonProperty @JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class) private IdentityKey identityKey; - public AciIdentityPair() {} + public ServiceIdentityPair() {} // Visible for testing - public AciIdentityPair(ACI aci, IdentityKey identityKey) { - this.aci = aci; + public ServiceIdentityPair(ServiceId serviceId, IdentityKey identityKey) { + this.serviceId = serviceId; this.identityKey = identityKey; } - public @Nullable ACI getAci() { - return aci; + public @Nullable ServiceId getServiceId() { + return serviceId; } public @Nullable IdentityKey getIdentityKey() { From e3ec53c2d01dbc2c781c010a6dd924a37a18d13e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 31 Jul 2023 15:47:27 -0400 Subject: [PATCH 16/66] Remove deprecated SMS fields from recipient table. --- .../securesms/database/MmsHelper.kt | 2 - .../securesms/AppInitialization.java | 3 - .../securesms/InviteActivity.java | 7 +- .../thoughtcrime/securesms/MainNavigator.java | 5 - .../reminder/FirstInviteReminder.java | 25 -- .../reminder/SecondInviteReminder.java | 37 --- .../contacts/paged/ContactSearchAdapter.kt | 2 +- .../ConversationParentFragment.java | 51 +--- .../v2/keyboard/AttachmentKeyboardFragment.kt | 3 +- .../ConversationListFragment.java | 13 - .../securesms/database/MessageTable.kt | 32 --- .../securesms/database/RecipientTable.kt | 77 ----- .../database/model/RecipientRecord.kt | 10 - .../insights/InsightsAnimatorSetFactory.java | 94 ------ .../securesms/insights/InsightsConstants.java | 13 - .../InsightsDashboardDialogFragment.java | 271 ------------------ .../insights/InsightsDashboardState.java | 70 ----- .../insights/InsightsDashboardViewModel.java | 68 ----- .../securesms/insights/InsightsData.java | 19 -- .../InsightsInsecureRecipientsAdapter.java | 118 -------- .../securesms/insights/InsightsLauncher.java | 25 -- .../insights/InsightsModalDialogFragment.java | 132 --------- .../insights/InsightsModalState.java | 53 ---- .../insights/InsightsModalViewModel.java | 50 ---- .../securesms/insights/InsightsOptOut.java | 22 -- .../insights/InsightsRepository.java | 89 ------ .../insights/InsightsUserAvatar.java | 39 --- .../invites/InviteReminderModel.java | 143 --------- .../invites/InviteReminderRepository.java | 39 --- .../securesms/jobs/MmsSendJob.java | 6 +- .../securesms/jobs/TypingSendJob.java | 2 +- .../messages/MessageContentProcessor.java | 12 - .../messages/MessageContentProcessorV2.kt | 8 - .../securesms/mms/OutgoingMessage.kt | 10 +- .../notifications/RemoteReplyReceiver.java | 7 - .../securesms/notifications/ReplyMethod.java | 13 +- .../notifications/v2/NotificationBuilder.kt | 3 +- .../securesms/recipients/Recipient.java | 28 -- .../recipients/RecipientDetails.java | 9 - .../securesms/recipients/RecipientUtil.java | 2 - .../service/QuickResponseService.java | 5 +- .../securesms/sharing/MultiShareSender.java | 54 +--- .../ShareInterstitialActivity.java | 2 +- .../securesms/sharing/v2/ShareActivity.kt | 3 +- .../main/res/layout/insights_dashboard.xml | 200 ------------- .../insights_dashboard_adapter_item.xml | 47 --- app/src/main/res/layout/insights_modal.xml | 115 -------- app/src/main/res/menu/text_secure_normal.xml | 4 - app/src/main/res/values/ids.xml | 1 - app/src/main/res/values/strings.xml | 27 -- .../database/RecipientDatabaseTestUtils.kt | 6 - .../securesms/database/TestMms.kt | 2 - 52 files changed, 34 insertions(+), 2044 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/reminder/FirstInviteReminder.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/reminder/SecondInviteReminder.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsAnimatorSetFactory.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsConstants.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardDialogFragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardState.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardViewModel.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsData.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsInsecureRecipientsAdapter.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsLauncher.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalDialogFragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalState.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalViewModel.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsOptOut.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/insights/InsightsUserAvatar.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java delete mode 100644 app/src/main/res/layout/insights_dashboard.xml delete mode 100644 app/src/main/res/layout/insights_dashboard_adapter_item.xml delete mode 100644 app/src/main/res/layout/insights_modal.xml diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsHelper.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsHelper.kt index 9d952f08e8..82b487a32b 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsHelper.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsHelper.kt @@ -17,7 +17,6 @@ object MmsHelper { recipient: Recipient = Recipient.UNKNOWN, body: String = "body", sentTimeMillis: Long = System.currentTimeMillis(), - subscriptionId: Int = -1, expiresIn: Long = 0, viewOnce: Boolean = false, distributionType: Int = ThreadTable.DistributionTypes.DEFAULT, @@ -32,7 +31,6 @@ object MmsHelper { recipient = recipient, body = body, timestamp = sentTimeMillis, - subscriptionId = subscriptionId, expiresIn = expiresIn, viewOnce = viewOnce, distributionType = distributionType, diff --git a/app/src/main/java/org/thoughtcrime/securesms/AppInitialization.java b/app/src/main/java/org/thoughtcrime/securesms/AppInitialization.java index 98a1e31e12..5daf39db0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/AppInitialization.java +++ b/app/src/main/java/org/thoughtcrime/securesms/AppInitialization.java @@ -6,7 +6,6 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.insights.InsightsOptOut; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob; @@ -30,7 +29,6 @@ private AppInitialization() {} public static void onFirstEverAppLaunch(@NonNull Context context) { Log.i(TAG, "onFirstEverAppLaunch()"); - InsightsOptOut.userRequestedOptOut(context); TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION); TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION); TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode()); @@ -71,7 +69,6 @@ public static void onPostBackupRestore(@NonNull Context context) { public static void onRepairFirstEverAppLaunch(@NonNull Context context) { Log.w(TAG, "onRepairFirstEverAppLaunch()"); - InsightsOptOut.userRequestedOptOut(context); TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION); TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION); TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java index 0d310ad11b..23d0c4156b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java @@ -254,13 +254,8 @@ protected Void doInBackground(SelectedContact... contacts) { for (SelectedContact contact : contacts) { RecipientId recipientId = contact.getOrCreateRecipientId(context); Recipient recipient = Recipient.resolved(recipientId); - int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1); - MessageSender.send(context, OutgoingMessage.sms(recipient, message, subscriptionId), -1L, MessageSender.SendType.SMS, null, null); - - if (recipient.getContactUri() != null) { - SignalDatabase.recipients().setHasSentInvite(recipient.getId()); - } + MessageSender.send(context, OutgoingMessage.sms(recipient, message), -1L, MessageSender.SendType.SMS, null, null); } return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java b/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java index fea48a4a8c..0d7e28c24e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java @@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity; import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; -import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.recipients.RecipientId; import io.reactivex.rxjava3.disposables.Disposable; @@ -78,10 +77,6 @@ public void goToInvite() { activity.startActivity(intent); } - public void goToInsights() { - InsightsLauncher.showInsightsDashboard(activity.getSupportFragmentManager()); - } - private @NonNull FragmentManager getFragmentManager() { return activity.getSupportFragmentManager(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/FirstInviteReminder.java b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/FirstInviteReminder.java deleted file mode 100644 index 5c1e6859d5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/FirstInviteReminder.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.thoughtcrime.securesms.components.reminder; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.R; - -public final class FirstInviteReminder extends Reminder { - - private final int percentIncrease; - - public FirstInviteReminder(final int percentIncrease) { - super(R.string.FirstInviteReminder__title, NO_RESOURCE); - this.percentIncrease = percentIncrease; - - addAction(new Action(R.string.InsightsReminder__invite, R.id.reminder_action_invite)); - addAction(new Action(R.string.InsightsReminder__view_insights, R.id.reminder_action_view_insights)); - } - - @Override - public @NonNull CharSequence getText(@NonNull Context context) { - return context.getString(R.string.FirstInviteReminder__description, percentIncrease); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/SecondInviteReminder.java b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/SecondInviteReminder.java deleted file mode 100644 index fb6a7af35b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/SecondInviteReminder.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.thoughtcrime.securesms.components.reminder; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.recipients.Recipient; - -public final class SecondInviteReminder extends Reminder { - - private final Recipient recipient; - private final int progress; - - public SecondInviteReminder(final @NonNull Context context, - final @NonNull Recipient recipient, - final int percent) - { - super(R.string.SecondInviteReminder__title, NO_RESOURCE); - this.recipient = recipient; - - this.progress = percent; - - addAction(new Action(R.string.InsightsReminder__invite, R.id.reminder_action_invite)); - addAction(new Action(R.string.InsightsReminder__view_insights, R.id.reminder_action_view_insights)); - } - - @Override - public @NonNull CharSequence getText(@NonNull Context context) { - return context.getString(R.string.SecondInviteReminder__description, recipient.getDisplayName(context)); - } - - @Override - public int getProgress() { - return progress; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt index 12e6961597..ff1cbbcf85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt @@ -573,7 +573,7 @@ open class ContactSearchAdapter( } private fun isSmsContact(model: T): Boolean { - return (getRecipient(model).isForceSmsSelection || getRecipient(model).isUnregistered) && !getRecipient(model).isDistributionList + return getRecipient(model).isUnregistered && !getRecipient(model).isDistributionList } private fun isNotRegistered(model: T): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 4c6d892036..3b55dfbdf6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -184,10 +184,7 @@ import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity; import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment; import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationSuggestionsDialog; -import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.invites.InviteActions; -import org.thoughtcrime.securesms.invites.InviteReminderModel; -import org.thoughtcrime.securesms.invites.InviteReminderRepository; import org.thoughtcrime.securesms.jobs.ForceUpdateGroupV2Job; import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob; import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob; @@ -437,7 +434,6 @@ public class ConversationParentFragment extends Fragment private ConversationSearchViewModel searchViewModel; private ConversationStickerViewModel stickerViewModel; private ConversationViewModel viewModel; - private InviteReminderModel inviteReminderModel; private ConversationGroupViewModel groupViewModel; private MentionsPickerViewModel mentionsViewModel; private InlineQueryViewModel inlineQueryViewModel; @@ -1364,8 +1360,7 @@ private void updatePaymentsAvailable() { if (paymentsValues.getPaymentsAvailability().isSendAllowed() && !recipient.get().isSelf() && !recipient.get().isGroup() && - recipient.get().isRegistered() && - !recipient.get().isForceSmsSelection()) + recipient.get().isRegistered()) { attachmentKeyboardStub.get().filterAttachmentKeyboardButtons(null); } else { @@ -1425,16 +1420,11 @@ private void handleSecurityChange(@NonNull ConversationSecurityInfo conversation sendButton.disableTransportType(MessageSendType.TransportType.SIGNAL); } - if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection() && smsEnabled) { + if (isPushAvailable || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) { + sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL); + } else { sendButton.setDefaultTransport(MessageSendType.TransportType.SMS); viewModel.insertSmsExportUpdateEvent(recipient.get()); - } else { - if (isPushAvailable || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) { - sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL); - } else { - sendButton.setDefaultTransport(MessageSendType.TransportType.SMS); - viewModel.insertSmsExportUpdateEvent(recipient.get()); - } } calculateCharactersRemaining(); @@ -1710,12 +1700,9 @@ public void onSuccess(Boolean result) { private void onSecurityUpdated() { Log.i(TAG, "onSecurityUpdated()"); updateReminders(); - updateDefaultSubscriptionId(recipient.get().getDefaultSubscriptionId()); } private void initializeInsightObserver() { - inviteReminderModel = new InviteReminderModel(requireContext(), new InviteReminderRepository(requireContext())); - inviteReminderModel.loadReminder(recipient, this::updateReminders); } protected void updateReminders() { @@ -1724,7 +1711,6 @@ protected void updateReminders() { return; } - Optional inviteReminder = inviteReminderModel.getReminder(); Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue(); List gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue(); @@ -1740,11 +1726,8 @@ protected void updateReminders() { } else if (SignalStore.account().isRegistered() && TextSecurePreferences.isShowInviteReminders(context) && !viewModel.isPushAvailable() && - inviteReminder.isPresent() && !recipient.get().isGroup()) { reminderView.get().setOnActionClickListener(this::handleReminderAction); - reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder()); - reminderView.get().showReminder(inviteReminder.get()); } else if (actionableRequestingMembers != null && actionableRequestingMembers > 0) { reminderView.get().showReminder(new PendingGroupJoinRequestsReminder(actionableRequestingMembers)); reminderView.get().setOnActionClickListener(id -> { @@ -1785,8 +1768,6 @@ private void handleReminderAction(@IdRes int reminderActionId) { if (reminderActionId == R.id.reminder_action_invite) { handleInviteLink(); reminderView.get().requestDismiss(); - } else if (reminderActionId == R.id.reminder_action_view_insights) { - InsightsLauncher.showInsightsDashboard(getChildFragmentManager()); } else if (reminderActionId == R.id.reminder_action_update_now) { PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); } else if (reminderActionId == R.id.reminder_action_re_register) { @@ -1958,8 +1939,6 @@ public boolean canSchedule() { composeText.setMessageSendType(newMessageSendType); updateSendButtonColor(newMessageSendType); - - if (manuallySelected) recordTransportPreference(newMessageSendType); }); titleView.setOnStoryRingClickListener(v -> handleStoryRingClick()); @@ -2461,7 +2440,6 @@ private void onRecipientChanged(@NonNull Recipient recipient) { titleView.setVerified(identityRecords.isVerified() && !recipient.isSelf()); setBlockedUserState(recipient, viewModel.getConversationStateSnapshot().getSecurityInfo()); updateReminders(); - updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); updatePaymentsAvailable(); updateSendButtonColor(sendButton.getSelectedSendType()); @@ -2904,7 +2882,6 @@ private void sendMediaMessage(@NonNull MediaSendActivityResult result) { result.getBody(), Collections.emptyList(), System.currentTimeMillis(), - -1, expiresIn, result.isViewOnce(), distributionType, @@ -3033,7 +3010,6 @@ private ListenableFuture sendMediaMessage(@NonNull RecipientId recipientId OutgoingMessage.buildMessage(slideDeck, body), slideDeck.asAttachments(), System.currentTimeMillis(), - sendType.getSimSubscriptionIdOr(-1), expiresIn, viewOnce, distributionType, @@ -3126,7 +3102,7 @@ private void sendTextMessage(@NonNull MessageSendType sendType, } ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread); } else { - message = OutgoingMessage.sms(recipient.get(), messageBody, sendType.getSimSubscriptionIdOr(-1)); + message = OutgoingMessage.sms(recipient.get(), messageBody); } Permissions.with(this) @@ -3246,23 +3222,6 @@ private void updateScheduledMessagesBar(int count) { } } - private void recordTransportPreference(MessageSendType sendType) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - RecipientTable recipientTable = SignalDatabase.recipients(); - - recipientTable.setDefaultSubscriptionId(recipient.getId(), sendType.getSimSubscriptionIdOr(-1)); - - if (!recipient.resolve().isPushGroup()) { - recipientTable.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && sendType.usesSmsTransport()); - } - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - @Override public void onRecorderPermissionRequired() { Permissions.with(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/keyboard/AttachmentKeyboardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/keyboard/AttachmentKeyboardFragment.kt index 094c459a12..f386ce1f6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/keyboard/AttachmentKeyboardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/keyboard/AttachmentKeyboardFragment.kt @@ -104,8 +104,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_ if (paymentsValues.paymentsAvailability.isSendAllowed && !recipient.isSelf && !recipient.isGroup && - recipient.isRegistered && - !recipient.isForceSmsSelection + recipient.isRegistered ) { attachmentKeyboardView.filterAttachmentKeyboardButtons(null) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 43578c6073..de92d57abd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -138,7 +138,6 @@ import org.thoughtcrime.securesms.events.ReminderUpdateEvent; import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs; import org.thoughtcrime.securesms.groups.SelectionLimits; -import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; @@ -472,10 +471,6 @@ public void onResume() { itemAnimator.disable(); SpoilerAnnotation.resetRevealedSpoilers(); - if (Util.isDefaultSmsProvider(requireContext())) { - InsightsLauncher.showInsightsModal(requireContext(), requireFragmentManager()); - } - if ((!requireCallback().getSearchToolbar().resolved() || !(requireCallback().getSearchToolbar().get().getVisibility() == View.VISIBLE)) && list.getAdapter() != defaultAdapter) { setAdapter(defaultAdapter); } @@ -561,7 +556,6 @@ public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflat @Override public void onPrepareOptionsMenu(Menu menu) { - menu.findItem(R.id.menu_insights).setVisible(Util.isDefaultSmsProvider(requireContext())); menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(requireContext())); ConversationFilterRequest request = viewModel.getConversationFilterRequest(); @@ -592,9 +586,6 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { } else if (itemId == R.id.menu_invite) { handleInvite(); return true; - } else if (itemId == R.id.menu_insights) { - handleInsights(); - return true; } else if (itemId == R.id.menu_notification_profile) { handleNotificationProfile(); return true; @@ -1143,10 +1134,6 @@ private void handleInvite() { getNavigator().goToInvite(); } - private void handleInsights() { - getNavigator().goToInsights(); - } - private void handleNotificationProfile() { NotificationProfileSelectionFragment.show(getParentFragmentManager()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index b1417a8495..ab3922399e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -112,7 +112,6 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchove import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange -import org.thoughtcrime.securesms.insights.InsightsConstants import org.thoughtcrime.securesms.jobs.OptimizeMessageSearchIndexJob import org.thoughtcrime.securesms.jobs.ThreadUpdateJob import org.thoughtcrime.securesms.jobs.TrimThreadJob @@ -1084,10 +1083,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat if (unread && editedMessage == null) { threads.incrementUnread(threadId, 1, 0) } - - if (message.subscriptionId != -1) { - recipients.setDefaultSubscriptionId(message.authorId, message.subscriptionId) - } } id @@ -2507,7 +2502,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat body = body, attachments = attachments, timestamp = timestamp, - subscriptionId = subscriptionId, expiresIn = expiresIn, viewOnce = viewOnce, distributionType = distributionType, @@ -3712,15 +3706,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat .readToSingleInt() } - fun getInsecureMessageSentCount(threadId: Long): Int { - return readableDatabase - .select("COUNT(*)") - .from(TABLE_NAME) - .where("$THREAD_ID = ? AND $outgoingInsecureMessageClause AND $DATE_SENT > ?", threadId, (System.currentTimeMillis() - InsightsConstants.PERIOD_IN_MILLIS)) - .run() - .readToSingleInt() - } - fun getSecureMessageCount(threadId: Long): Int { return readableDatabase .select("COUNT(*)") @@ -3739,14 +3724,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat .readToSingleInt() } - fun getInsecureMessageCountForInsights(): Int { - return getMessageCountForRecipientsAndType(outgoingInsecureMessageClause) - } - - fun getSecureMessageCountForInsights(): Int { - return getMessageCountForRecipientsAndType(outgoingSecureMessageClause) - } - private fun hasSmsExportMessage(threadId: Long): Boolean { return readableDatabase .exists(TABLE_NAME) @@ -3754,15 +3731,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat .run() } - private fun getMessageCountForRecipientsAndType(typeClause: String): Int { - return readableDatabase - .select("COUNT(*)") - .from(TABLE_NAME) - .where("$typeClause AND $DATE_SENT > ?", (System.currentTimeMillis() - InsightsConstants.PERIOD_IN_MILLIS)) - .run() - .readToSingleInt() - } - private val outgoingInsecureMessageClause = "($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} AND NOT ($TYPE & ${MessageTypes.SECURE_MESSAGE_BIT})" private val outgoingSecureMessageClause = "($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} AND ($TYPE & ${MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT})" diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index 813a9267f1..704c8d7393 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -285,8 +285,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da CALL_VIBRATE, MUTE_UNTIL, AVATAR_COLOR, - SEEN_INVITE_REMINDER, - DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, PROFILE_KEY, @@ -305,7 +303,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da LAST_PROFILE_FETCH, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, - FORCE_SMS_SELECTION, CAPABILITIES, STORAGE_SERVICE_ID, MENTION_SETTING, @@ -396,21 +393,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da """ ) - private val INSIGHTS_INVITEE_LIST = - """ - SELECT $TABLE_NAME.$ID - FROM $TABLE_NAME INNER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} - WHERE - $TABLE_NAME.$GROUP_ID IS NULL AND - $TABLE_NAME.$REGISTERED = ${RegisteredState.NOT_REGISTERED.id} AND - $TABLE_NAME.$SEEN_INVITE_REMINDER < ${InsightsBannerTier.TIER_TWO.id} AND - ${ThreadTable.TABLE_NAME}.${ThreadTable.HAS_SENT} AND - ${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} > ? AND - ${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1 AND - $TABLE_NAME.$HIDDEN = 0 - ORDER BY ${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC LIMIT 50 - """ - /** Used as a placeholder recipient for self during migrations when self isn't yet available. */ private val PLACEHOLDER_SELF_ID = -2L } @@ -1348,24 +1330,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } } - fun setDefaultSubscriptionId(id: RecipientId, defaultSubscriptionId: Int) { - val values = ContentValues().apply { - put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId) - } - if (update(id, values)) { - ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) - } - } - - fun setForceSmsSelection(id: RecipientId, forceSmsSelection: Boolean) { - val contentValues = ContentValues(1).apply { - put(FORCE_SMS_SELECTION, if (forceSmsSelection) 1 else 0) - } - if (update(id, contentValues)) { - ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) - } - } - fun setBlocked(id: RecipientId, blocked: Boolean) { val values = ContentValues().apply { put(BLOCKED, if (blocked) 1 else 0) @@ -1452,29 +1416,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da StorageSyncHelper.scheduleSyncForDataChange() } - fun setSeenFirstInviteReminder(id: RecipientId) { - setInsightsBannerTier(id, InsightsBannerTier.TIER_ONE) - } - - fun setSeenSecondInviteReminder(id: RecipientId) { - setInsightsBannerTier(id, InsightsBannerTier.TIER_TWO) - } - - fun setHasSentInvite(id: RecipientId) { - setSeenSecondInviteReminder(id) - } - - private fun setInsightsBannerTier(id: RecipientId, insightsBannerTier: InsightsBannerTier) { - val query = "$ID = ? AND $SEEN_INVITE_REMINDER < ?" - val args = arrayOf(id.serialize(), insightsBannerTier.toString()) - val values = ContentValues(1).apply { - put(SEEN_INVITE_REMINDER, insightsBannerTier.id) - } - - writableDatabase.update(TABLE_NAME, values, query, args) - ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) - } - fun setExpireMessages(id: RecipientId, expiration: Int) { val values = ContentValues(1).apply { put(MESSAGE_EXPIRATION_TIME, expiration) @@ -3096,19 +3037,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return operations } - fun getUninvitedRecipientsForInsights(): List { - val results: MutableList = LinkedList() - val args = arrayOf((System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)).toString()) - - readableDatabase.rawQuery(INSIGHTS_INVITEE_LIST, args).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))) - } - } - - return results - } - fun getRegistered(): List { val results: MutableList = LinkedList() @@ -3899,8 +3827,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da CHAT_COLORS to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null), AVATAR_COLOR to primaryRecord.avatarColor.serialize(), CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null), - SEEN_INVITE_REMINDER to secondaryRecord.insightsBannerTier.id, - DEFAULT_SUBSCRIPTION_ID to secondaryRecord.getDefaultSubscriptionId().orElse(-1), MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages, REGISTERED to RegisteredState.REGISTERED.id, SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName, @@ -4246,7 +4172,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da callVibrateState = VibrateState.fromId(cursor.requireInt(CALL_VIBRATE)), messageRingtone = Util.uri(cursor.requireString(MESSAGE_RINGTONE)), callRingtone = Util.uri(cursor.requireString(CALL_RINGTONE)), - defaultSubscriptionId = cursor.requireInt(DEFAULT_SUBSCRIPTION_ID), expireMessages = cursor.requireInt(MESSAGE_EXPIRATION_TIME), registered = RegisteredState.fromId(cursor.requireInt(REGISTERED)), profileKey = profileKey, @@ -4263,9 +4188,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da lastProfileFetch = cursor.requireLong(LAST_PROFILE_FETCH), notificationChannel = cursor.requireString(NOTIFICATION_CHANNEL), unidentifiedAccessMode = UnidentifiedAccessMode.fromMode(cursor.requireInt(UNIDENTIFIED_ACCESS_MODE)), - forceSmsSelection = cursor.requireBoolean(FORCE_SMS_SELECTION), capabilities = readCapabilities(cursor), - insightsBannerTier = InsightsBannerTier.fromId(cursor.requireInt(SEEN_INVITE_REMINDER)), storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)), mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)), wallpaper = chatWallpaper, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt index 7cafb8cfda..ae190c8379 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt @@ -8,7 +8,6 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus import org.thoughtcrime.securesms.database.RecipientTable -import org.thoughtcrime.securesms.database.RecipientTable.InsightsBannerTier import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode @@ -22,7 +21,6 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaper import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.PNI -import java.util.Optional /** * Database model for [RecipientTable]. @@ -43,7 +41,6 @@ data class RecipientRecord( val callVibrateState: VibrateState, val messageRingtone: Uri?, val callRingtone: Uri?, - private val defaultSubscriptionId: Int, val expireMessages: Int, val registered: RegisteredState, val profileKey: ByteArray?, @@ -63,10 +60,7 @@ data class RecipientRecord( val lastProfileFetch: Long, val notificationChannel: String?, val unidentifiedAccessMode: UnidentifiedAccessMode, - @get:JvmName("isForceSmsSelection") - val forceSmsSelection: Boolean, val capabilities: Capabilities, - val insightsBannerTier: InsightsBannerTier, val storageId: ByteArray?, val mentionSetting: MentionSetting, val wallpaper: ChatWallpaper?, @@ -85,10 +79,6 @@ data class RecipientRecord( val callLinkRoomId: CallLinkRoomId? ) { - fun getDefaultSubscriptionId(): Optional { - return if (defaultSubscriptionId != -1) Optional.of(defaultSubscriptionId) else Optional.empty() - } - fun e164Only(): Boolean { return this.e164 != null && this.aci == null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsAnimatorSetFactory.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsAnimatorSetFactory.java deleted file mode 100644 index a46499ef47..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsAnimatorSetFactory.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.view.animation.DecelerateInterpolator; - -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -final class InsightsAnimatorSetFactory { - private static final int PROGRESS_ANIMATION_DURATION = 800; - private static final int DETAILS_ANIMATION_DURATION = 200; - private static final int PERCENT_SECURE_ANIMATION_DURATION = 400; - private static final int LOTTIE_ANIMATION_DURATION = 1500; - private static final int ANIMATION_START_DELAY = PROGRESS_ANIMATION_DURATION - DETAILS_ANIMATION_DURATION; - private static final float PERCENT_SECURE_MAX_SCALE = 1.3f; - - private InsightsAnimatorSetFactory() { - } - - static AnimatorSet create(int insecurePercent, - @Nullable final UpdateListener progressUpdateListener, - @Nullable final UpdateListener detailsUpdateListener, - @Nullable final UpdateListener percentSecureListener, - @Nullable final UpdateListener lottieListener) - { - final int securePercent = 100 - insecurePercent; - final AnimatorSet animatorSet = new AnimatorSet(); - final ValueAnimator[] animators = Stream.of(createProgressAnimator(securePercent, progressUpdateListener), - createDetailsAnimator(detailsUpdateListener), - createPercentSecureAnimator(percentSecureListener), - createLottieAnimator(lottieListener)) - .filter(a -> a != null) - .toArray(ValueAnimator[]::new); - - animatorSet.setInterpolator(new DecelerateInterpolator()); - animatorSet.playTogether(animators); - - return animatorSet; - } - - private static @Nullable Animator createProgressAnimator(int securePercent, @Nullable UpdateListener updateListener) { - if (updateListener == null) return null; - - final ValueAnimator progressAnimator = ValueAnimator.ofFloat(0, securePercent / 100f); - - progressAnimator.setDuration(PROGRESS_ANIMATION_DURATION); - progressAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue())); - - return progressAnimator; - } - - private static @Nullable Animator createDetailsAnimator(@Nullable UpdateListener updateListener) { - if (updateListener == null) return null; - - final ValueAnimator detailsAnimator = ValueAnimator.ofFloat(0, 1f); - - detailsAnimator.setDuration(DETAILS_ANIMATION_DURATION); - detailsAnimator.setStartDelay(ANIMATION_START_DELAY); - detailsAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue())); - - return detailsAnimator; - } - - private static @Nullable Animator createPercentSecureAnimator(@Nullable UpdateListener updateListener) { - if (updateListener == null) return null; - - final ValueAnimator percentSecureAnimator = ValueAnimator.ofFloat(1f, PERCENT_SECURE_MAX_SCALE, 1f); - - percentSecureAnimator.setStartDelay(ANIMATION_START_DELAY); - percentSecureAnimator.setDuration(PERCENT_SECURE_ANIMATION_DURATION); - percentSecureAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue())); - - return percentSecureAnimator; - } - - private static @Nullable Animator createLottieAnimator(@Nullable UpdateListener updateListener) { - if (updateListener == null) return null; - - final ValueAnimator lottieAnimator = ValueAnimator.ofFloat(0, 1f); - - lottieAnimator.setStartDelay(ANIMATION_START_DELAY); - lottieAnimator.setDuration(LOTTIE_ANIMATION_DURATION); - lottieAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue())); - - return lottieAnimator; - } - - interface UpdateListener { - void onUpdate(float value); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsConstants.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsConstants.java deleted file mode 100644 index 40435b33d5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsConstants.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import java.util.concurrent.TimeUnit; - -public final class InsightsConstants { - - public static final long PERIOD_IN_DAYS = 7L; - public static final long PERIOD_IN_MILLIS = TimeUnit.DAYS.toMillis(PERIOD_IN_DAYS); - - private InsightsConstants() { - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardDialogFragment.java deleted file mode 100644 index bcabaeb806..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardDialogFragment.java +++ /dev/null @@ -1,271 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.DialogFragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.RecyclerView; - -import com.airbnb.lottie.LottieAnimationView; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.thoughtcrime.securesms.NewConversationActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.ArcProgressBar; -import org.thoughtcrime.securesms.components.AvatarImageView; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.ThemeUtil; - -import java.util.List; - -public final class InsightsDashboardDialogFragment extends DialogFragment { - - private TextView securePercentage; - private ArcProgressBar progress; - private View progressContainer; - private TextView tagline; - private TextView encryptedMessages; - private TextView title; - private TextView description; - private RecyclerView insecureRecipients; - private TextView locallyGenerated; - private AvatarImageView avatarImageView; - private InsightsInsecureRecipientsAdapter adapter; - private LottieAnimationView lottieAnimationView; - private AnimatorSet animatorSet; - private Button startAConversation; - private Toolbar toolbar; - private InsightsDashboardViewModel viewModel; - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - requireFragmentManager().beginTransaction() - .detach(this) - .attach(this) - .commit(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (ThemeUtil.isDarkTheme(requireActivity())) { - setStyle(STYLE_NO_FRAME, R.style.TextSecure_DarkTheme); - } else { - setStyle(STYLE_NO_FRAME, R.style.TextSecure_LightTheme); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.insights_dashboard, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - securePercentage = view.findViewById(R.id.insights_dashboard_percent_secure); - progress = view.findViewById(R.id.insights_dashboard_progress); - progressContainer = view.findViewById(R.id.insights_dashboard_percent_container); - encryptedMessages = view.findViewById(R.id.insights_dashboard_encrypted_messages); - tagline = view.findViewById(R.id.insights_dashboard_tagline); - title = view.findViewById(R.id.insights_dashboard_make_signal_secure); - description = view.findViewById(R.id.insights_dashboard_invite_your_contacts); - insecureRecipients = view.findViewById(R.id.insights_dashboard_recycler); - locallyGenerated = view.findViewById(R.id.insights_dashboard_this_stat_was_generated_locally); - avatarImageView = view.findViewById(R.id.insights_dashboard_avatar); - startAConversation = view.findViewById(R.id.insights_dashboard_start_a_conversation); - lottieAnimationView = view.findViewById(R.id.insights_dashboard_lottie_animation); - toolbar = view.findViewById(R.id.insights_dashboard_toolbar); - - setupStartAConversation(); - setDashboardDetailsAlpha(0f); - setNotEnoughDataAlpha(0f); - setupToolbar(); - setupRecycler(); - initializeViewModel(); - } - - private void setupStartAConversation() { - startAConversation.setOnClickListener(v -> startActivity(new Intent(requireActivity(), NewConversationActivity.class))); - } - - private void setDashboardDetailsAlpha(float alpha) { - tagline.setAlpha(alpha); - title.setAlpha(alpha); - description.setAlpha(alpha); - insecureRecipients.setAlpha(alpha); - locallyGenerated.setAlpha(alpha); - encryptedMessages.setAlpha(alpha); - } - - private void setupToolbar() { - toolbar.setNavigationOnClickListener(v -> dismiss()); - } - - private void setupRecycler() { - adapter = new InsightsInsecureRecipientsAdapter(this::handleInviteRecipient); - insecureRecipients.setAdapter(adapter); - } - - private void initializeViewModel() { - final InsightsDashboardViewModel.Repository repository = new InsightsRepository(requireContext()); - final InsightsDashboardViewModel.Factory factory = new InsightsDashboardViewModel.Factory(repository); - - viewModel = new ViewModelProvider(this, factory).get(InsightsDashboardViewModel.class); - - viewModel.getState().observe(getViewLifecycleOwner(), state -> { - updateInsecurePercent(state.getData()); - updateInsecureRecipients(state.getInsecureRecipients()); - updateUserAvatar(state.getUserAvatar()); - }); - } - - private void updateInsecurePercent(@Nullable InsightsData insightsData) { - if (insightsData == null) return; - - if (insightsData.hasEnoughData()) { - setTitleAndDescriptionText(insightsData.getPercentInsecure()); - animateProgress(insightsData.getPercentInsecure()); - } else { - setNotEnoughDataText(); - animateNotEnoughData(); - } - } - - private void animateProgress(int insecurePercent) { - startAConversation.setVisibility(View.GONE); - if (animatorSet == null) { - animatorSet = InsightsAnimatorSetFactory.create(insecurePercent, - this::setProgressPercentage, - this::setDashboardDetailsAlpha, - this::setPercentSecureScale, - insecurePercent == 0 ? this::setLottieProgress : null); - - if (insecurePercent == 0) { - animatorSet.addListener(new ToolbarBackgroundColorAnimationListener()); - } - - animatorSet.start(); - } - } - - private void setProgressPercentage(float percent) { - securePercentage.setText(String.valueOf(Math.round(percent * 100))); - progress.setProgress(percent); - } - - private void setPercentSecureScale(float scale) { - progressContainer.setScaleX(scale); - progressContainer.setScaleY(scale); - } - - private void setLottieProgress(float progress) { - lottieAnimationView.setProgress(progress); - } - - private void setTitleAndDescriptionText(int insecurePercent) { - startAConversation.setVisibility(View.GONE); - progressContainer.setVisibility(View.VISIBLE); - insecureRecipients.setVisibility(View.VISIBLE); - encryptedMessages.setText(R.string.InsightsDashboardFragment__encrypted_messages); - tagline.setText(getString(R.string.InsightsDashboardFragment__signal_protocol_automatically_protected, 100 - insecurePercent, InsightsConstants.PERIOD_IN_DAYS)); - - if (insecurePercent == 0) { - lottieAnimationView.setVisibility(View.VISIBLE); - title.setVisibility(View.GONE); - description.setVisibility(View.GONE); - } else { - lottieAnimationView.setVisibility(View.GONE); - title.setText(R.string.InsightsDashboardFragment__spread_the_word); - description.setText(R.string.InsightsDashboardFragment__invite_your_contacts); - title.setVisibility(View.VISIBLE); - description.setVisibility(View.VISIBLE); - } - } - - private void setNotEnoughDataText() { - startAConversation.setVisibility(View.VISIBLE); - progressContainer.setVisibility(View.INVISIBLE); - insecureRecipients.setVisibility(View.GONE); - encryptedMessages.setText(R.string.InsightsDashboardFragment__not_enough_data); - tagline.setText(getString(R.string.InsightsDashboardFragment__your_insights_percentage_is_calculated_based_on, InsightsConstants.PERIOD_IN_DAYS)); - } - - private void animateNotEnoughData() { - if (animatorSet == null) { - animatorSet = InsightsAnimatorSetFactory.create(0, null, this::setNotEnoughDataAlpha, null, null); - animatorSet.start(); - } - } - - private void setNotEnoughDataAlpha(float alpha) { - encryptedMessages.setAlpha(alpha); - tagline.setAlpha(alpha); - startAConversation.setAlpha(alpha); - } - - private void updateInsecureRecipients(@NonNull List recipients) { - adapter.updateData(recipients); - } - - private void updateUserAvatar(@Nullable InsightsUserAvatar userAvatar) { - if (userAvatar == null) avatarImageView.setImageDrawable(null); - else userAvatar.load(avatarImageView); - } - - private void handleInviteRecipient(final @NonNull Recipient recipient) { - new MaterialAlertDialogBuilder(requireContext()) - .setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites, 1, 1)) - .setMessage(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url))) - .setPositiveButton(R.string.InsightsDashboardFragment__send, (dialog, which) -> viewModel.sendSmsInvite(recipient)) - .setNegativeButton(R.string.InsightsDashboardFragment__cancel, (dialog, which) -> dialog.dismiss()) - .show(); - } - - @Override - public void onDestroyView() { - if (animatorSet != null) { - animatorSet.cancel(); - animatorSet = null; - } - - super.onDestroyView(); - } - - private final class ToolbarBackgroundColorAnimationListener implements Animator.AnimatorListener { - - @Override - public void onAnimationStart(Animator animation) { - toolbar.setBackgroundResource(R.color.transparent); - } - - @Override - public void onAnimationEnd(Animator animation) { - toolbar.setBackgroundColor(ThemeUtil.getThemedColor(requireContext(), android.R.attr.windowBackground)); - } - - @Override - public void onAnimationCancel(Animator animation) { - toolbar.setBackgroundColor(ThemeUtil.getThemedColor(requireContext(), android.R.attr.windowBackground)); - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardState.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardState.java deleted file mode 100644 index f97933342c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardState.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.thoughtcrime.securesms.recipients.Recipient; - -import java.util.Collections; -import java.util.List; - -final class InsightsDashboardState { - - private final List insecureRecipients; - private final InsightsData insightsData; - private final InsightsUserAvatar userAvatar; - - private InsightsDashboardState(@NonNull Builder builder) { - this.insecureRecipients = builder.insecureRecipients; - this.insightsData = builder.insightsData; - this.userAvatar = builder.userAvatar; - } - - static @NonNull InsightsDashboardState.Builder builder() { - return new InsightsDashboardState.Builder(); - } - - @NonNull InsightsDashboardState.Builder buildUpon() { - return builder().withData(insightsData).withUserAvatar(userAvatar).withInsecureRecipients(insecureRecipients); - } - - @NonNull List getInsecureRecipients() { - return insecureRecipients; - } - - @Nullable InsightsUserAvatar getUserAvatar() { - return userAvatar; - } - - @Nullable InsightsData getData() { - return insightsData; - } - - static final class Builder { - private List insecureRecipients = Collections.emptyList(); - private InsightsUserAvatar userAvatar; - private InsightsData insightsData; - - private Builder() { - } - - @NonNull Builder withInsecureRecipients(@NonNull List insecureRecipients) { - this.insecureRecipients = insecureRecipients; - return this; - } - - @NonNull Builder withData(@NonNull InsightsData insightsData) { - this.insightsData = insightsData; - return this; - } - - @NonNull Builder withUserAvatar(@NonNull InsightsUserAvatar userAvatar) { - this.userAvatar = userAvatar; - return this; - } - - @NonNull InsightsDashboardState build() { - return new InsightsDashboardState(this); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardViewModel.java deleted file mode 100644 index af604a9cde..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsDashboardViewModel.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -import org.thoughtcrime.securesms.recipients.Recipient; - -import java.util.List; - -final class InsightsDashboardViewModel extends ViewModel { - - private final MutableLiveData internalState = new MutableLiveData<>(InsightsDashboardState.builder().build()); - private final Repository repository; - - private InsightsDashboardViewModel(@NonNull Repository repository) { - this.repository = repository; - - repository.getInsightsData(data -> internalState.setValue(getNewState(b -> b.withData(data)))); - repository.getUserAvatar(avatar -> internalState.setValue(getNewState(b -> b.withUserAvatar(avatar)))); - updateInsecureRecipients(); - } - - private void updateInsecureRecipients() { - repository.getInsecureRecipients(recipients -> internalState.setValue(getNewState(b -> b.withInsecureRecipients(recipients)))); - } - - @MainThread - private InsightsDashboardState getNewState(Consumer builderConsumer) { - InsightsDashboardState.Builder builder = internalState.getValue().buildUpon(); - builderConsumer.accept(builder); - return builder.build(); - } - - @NonNull LiveData getState() { - return internalState; - } - - public void sendSmsInvite(@NonNull Recipient recipient) { - repository.sendSmsInvite(recipient, this::updateInsecureRecipients); - } - - interface Repository { - void getInsightsData(@NonNull Consumer insightsDataConsumer); - void getInsecureRecipients(@NonNull Consumer> insecureRecipientsConsumer); - void getUserAvatar(@NonNull Consumer userAvatarConsumer); - void sendSmsInvite(@NonNull Recipient recipient, Runnable onSmsMessageSent); - } - - final static class Factory implements ViewModelProvider.Factory { - - private final Repository repository; - - Factory(@NonNull Repository repository) { - this.repository = repository; - } - - @NonNull - @Override - public T create(@NonNull Class modelClass) { - return (T) new InsightsDashboardViewModel(repository); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsData.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsData.java deleted file mode 100644 index 5fb4b5da32..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsData.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -final class InsightsData { - private final boolean hasEnoughData; - private final int percentInsecure; - - InsightsData(boolean hasEnoughData, int percentInsecure) { - this.hasEnoughData = hasEnoughData; - this.percentInsecure = percentInsecure; - } - - public boolean hasEnoughData() { - return hasEnoughData; - } - - public int getPercentInsecure() { - return percentInsecure; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsInsecureRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsInsecureRecipientsAdapter.java deleted file mode 100644 index d36e0be26a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsInsecureRecipientsAdapter.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.AvatarImageView; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.recipients.Recipient; - -import java.util.Collections; -import java.util.List; - -final class InsightsInsecureRecipientsAdapter extends RecyclerView.Adapter { - - private List data = Collections.emptyList(); - - private final Consumer onInviteClickedConsumer; - - InsightsInsecureRecipientsAdapter(Consumer onInviteClickedConsumer) { - this.onInviteClickedConsumer = onInviteClickedConsumer; - } - - public void updateData(List recipients) { - List oldData = data; - data = recipients; - - DiffUtil.calculateDiff(new DiffCallback(oldData, data)).dispatchUpdatesTo(this); - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.insights_dashboard_adapter_item, parent, false), this::handleInviteClicked); - } - - private void handleInviteClicked(@NonNull Integer position) { - onInviteClickedConsumer.accept(data.get(position)); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - holder.bind(data.get(position)); - } - - @Override - public int getItemCount() { - return data.size(); - } - - static final class ViewHolder extends RecyclerView.ViewHolder { - - private AvatarImageView avatarImageView; - private TextView displayName; - - private ViewHolder(@NonNull View itemView, Consumer onInviteClicked) { - super(itemView); - - avatarImageView = itemView.findViewById(R.id.recipient_avatar); - displayName = itemView.findViewById(R.id.recipient_display_name); - - Button invite = itemView.findViewById(R.id.recipient_invite); - invite.setOnClickListener(v -> { - int adapterPosition = getAdapterPosition(); - - if (adapterPosition == RecyclerView.NO_POSITION) return; - - onInviteClicked.accept(adapterPosition); - }); - } - - private void bind(@NonNull Recipient recipient) { - displayName.setText(recipient.getDisplayName(itemView.getContext())); - avatarImageView.setAvatar(GlideApp.with(itemView), recipient, false); - } - } - - private static class DiffCallback extends DiffUtil.Callback { - - private final List oldData; - private final List newData; - - private DiffCallback(@NonNull List oldData, - @NonNull List newData) - { - this.oldData = oldData; - this.newData = newData; - } - - @Override - public int getOldListSize() { - return oldData.size(); - } - - @Override - public int getNewListSize() { - return newData.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return oldData.get(oldItemPosition).getId() == newData.get(newItemPosition).getId(); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return oldData.get(oldItemPosition).equals(newData.get(newItemPosition)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsLauncher.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsLauncher.java deleted file mode 100644 index e5de5fee4d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsLauncher.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; - -public final class InsightsLauncher { - - private static final String MODAL_TAG = "modal.fragment"; - - public static void showInsightsModal(@NonNull Context context, @NonNull FragmentManager fragmentManager) { - if (InsightsOptOut.userHasOptedOut(context)) return; - - final Fragment fragment = fragmentManager.findFragmentByTag(MODAL_TAG); - - if (fragment == null) new InsightsModalDialogFragment().show(fragmentManager, MODAL_TAG); - } - - public static void showInsightsDashboard(@NonNull FragmentManager fragmentManager) { - new InsightsDashboardDialogFragment().show(fragmentManager, null); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalDialogFragment.java deleted file mode 100644 index 5249498477..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalDialogFragment.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.animation.AnimatorSet; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.res.Configuration; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.lifecycle.ViewModelProvider; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.ArcProgressBar; -import org.thoughtcrime.securesms.components.AvatarImageView; - -public final class InsightsModalDialogFragment extends DialogFragment { - - private ArcProgressBar progress; - private TextView securePercentage; - private AvatarImageView avatarImageView; - private AnimatorSet animatorSet; - private View progressContainer; - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - requireFragmentManager().beginTransaction() - .detach(this) - .attach(this) - .commit(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(STYLE_NO_FRAME, R.style.Theme_Signal_Insights_Modal); - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); - return dialog; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.insights_modal, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - View close = view.findViewById(R.id.insights_modal_close); - Button viewInsights = view.findViewById(R.id.insights_modal_view_insights); - - progress = view.findViewById(R.id.insights_modal_progress); - securePercentage = view.findViewById(R.id.insights_modal_percent_secure); - avatarImageView = view.findViewById(R.id.insights_modal_avatar); - progressContainer = view.findViewById(R.id.insights_modal_percent_container); - - close.setOnClickListener(v -> dismiss()); - viewInsights.setOnClickListener(v -> openInsightsAndDismiss()); - - initializeViewModel(); - } - - private void initializeViewModel() { - final InsightsModalViewModel.Repository repository = new InsightsRepository(requireContext()); - final InsightsModalViewModel.Factory factory = new InsightsModalViewModel.Factory(repository); - final InsightsModalViewModel viewModel = new ViewModelProvider(this, factory).get(InsightsModalViewModel.class); - - viewModel.getState().observe(getViewLifecycleOwner(), state -> { - updateInsecurePercent(state.getData()); - updateUserAvatar(state.getUserAvatar()); - }); - } - - private void updateInsecurePercent(@Nullable InsightsData insightsData) { - if (insightsData == null) return; - - if (animatorSet == null) { - animatorSet = InsightsAnimatorSetFactory.create(insightsData.getPercentInsecure(), this::setProgressPercentage, null, this::setPercentSecureScale, null); - animatorSet.start(); - } - } - - private void setProgressPercentage(float percent) { - securePercentage.setText(String.valueOf(Math.round(percent * 100))); - progress.setProgress(percent); - } - - private void setPercentSecureScale(float scale) { - progressContainer.setScaleX(scale); - progressContainer.setScaleY(scale); - } - - private void updateUserAvatar(@Nullable InsightsUserAvatar userAvatar) { - if (userAvatar == null) avatarImageView.setImageDrawable(null); - else userAvatar.load(avatarImageView); - } - - @Override - public void onDismiss(@NonNull DialogInterface dialog) { - super.onDismiss(dialog); - InsightsOptOut.userRequestedOptOut(requireContext()); - } - - private void openInsightsAndDismiss() { - InsightsLauncher.showInsightsDashboard(requireFragmentManager()); - dismiss(); - } - - @Override - public void onDestroyView() { - if (animatorSet != null) { - animatorSet.cancel(); - animatorSet = null; - } - - super.onDestroyView(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalState.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalState.java deleted file mode 100644 index 8ce6542628..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalState.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -final class InsightsModalState { - - private final InsightsData insightsData; - private final InsightsUserAvatar userAvatar; - - private InsightsModalState(@NonNull Builder builder) { - this.insightsData = builder.insightsData; - this.userAvatar = builder.userAvatar; - } - - static @NonNull InsightsModalState.Builder builder() { - return new InsightsModalState.Builder(); - } - - @NonNull InsightsModalState.Builder buildUpon() { - return builder().withUserAvatar(userAvatar).withData(insightsData); - } - - @Nullable InsightsUserAvatar getUserAvatar() { - return userAvatar; - } - - @Nullable InsightsData getData() { - return insightsData; - } - - static final class Builder { - private InsightsData insightsData; - private InsightsUserAvatar userAvatar; - - private Builder() { - } - - @NonNull Builder withData(@NonNull InsightsData insightsData) { - this.insightsData = insightsData; - return this; - } - - @NonNull Builder withUserAvatar(@NonNull InsightsUserAvatar userAvatar) { - this.userAvatar = userAvatar; - return this; - } - - @NonNull InsightsModalState build() { - return new InsightsModalState(this); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalViewModel.java deleted file mode 100644 index ad80c40847..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsModalViewModel.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -final class InsightsModalViewModel extends ViewModel { - - private final MutableLiveData internalState = new MutableLiveData<>(InsightsModalState.builder().build()); - - private InsightsModalViewModel(@NonNull Repository repository) { - repository.getInsightsData(data -> internalState.setValue(getNewState(b -> b.withData(data)))); - repository.getUserAvatar(avatar -> internalState.setValue(getNewState(b -> b.withUserAvatar(avatar)))); - } - - @MainThread - private InsightsModalState getNewState(Consumer builderConsumer) { - InsightsModalState.Builder builder = internalState.getValue().buildUpon(); - builderConsumer.accept(builder); - return builder.build(); - } - - @NonNull LiveData getState() { - return internalState; - } - - interface Repository { - void getInsightsData(Consumer insecurePercentConsumer); - void getUserAvatar(@NonNull Consumer userAvatarConsumer); - } - - final static class Factory implements ViewModelProvider.Factory { - - private final Repository repository; - - Factory(@NonNull Repository repository) { - this.repository = repository; - } - - @NonNull - @Override - public T create(@NonNull Class modelClass) { - return (T) new InsightsModalViewModel(repository); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsOptOut.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsOptOut.java deleted file mode 100644 index c3d90f227f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsOptOut.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -public final class InsightsOptOut { - private static final String INSIGHTS_OPT_OUT_PREFERENCE = "insights.opt.out"; - - private InsightsOptOut() { - } - - static boolean userHasOptedOut(@NonNull Context context) { - return TextSecurePreferences.getBooleanPreference(context, INSIGHTS_OPT_OUT_PREFERENCE, false); - } - - public static void userRequestedOptOut(@NonNull Context context) { - TextSecurePreferences.setBooleanPreference(context, INSIGHTS_OPT_OUT_PREFERENCE, true); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java deleted file mode 100644 index e6ef34e8b3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto; -import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; -import org.thoughtcrime.securesms.database.MessageTable; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.mms.OutgoingMessage; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.Util; -import org.signal.core.util.concurrent.SimpleTask; - -import java.util.List; -import java.util.Optional; - -public class InsightsRepository implements InsightsDashboardViewModel.Repository, InsightsModalViewModel.Repository { - - private final Context context; - - public InsightsRepository(Context context) { - this.context = context.getApplicationContext(); - } - - @Override - public void getInsightsData(@NonNull Consumer insightsDataConsumer) { - SimpleTask.run(() -> { - MessageTable messageTable = SignalDatabase.messages(); - int insecure = messageTable.getInsecureMessageCountForInsights(); - int secure = messageTable.getSecureMessageCountForInsights(); - - if (insecure + secure == 0) { - return new InsightsData(false, 0); - } else { - return new InsightsData(true, Util.clamp((int) Math.ceil((insecure * 100f) / (insecure + secure)), 0, 100)); - } - }, insightsDataConsumer::accept); - } - - @Override - public void getInsecureRecipients(@NonNull Consumer> insecureRecipientsConsumer) { - SimpleTask.run(() -> { - RecipientTable recipientTable = SignalDatabase.recipients(); - List unregisteredRecipients = recipientTable.getUninvitedRecipientsForInsights(); - - return Stream.of(unregisteredRecipients) - .map(Recipient::resolved) - .toList(); - }, - insecureRecipientsConsumer::accept); - } - - @Override - public void getUserAvatar(@NonNull Consumer avatarConsumer) { - SimpleTask.run(() -> { - Recipient self = Recipient.self().resolve(); - String name = Optional.of(self.getDisplayName(context)).orElse(""); - - return new InsightsUserAvatar(new ProfileContactPhoto(self), - self.getAvatarColor(), - new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40)); - }, avatarConsumer::accept); - } - - @Override - public void sendSmsInvite(@NonNull Recipient recipient, Runnable onSmsMessageSent) { - SimpleTask.run(() -> { - Recipient resolved = recipient.resolve(); - int subscriptionId = resolved.getDefaultSubscriptionId().orElse(-1); - String message = context.getString(R.string.InviteActivity_lets_switch_to_signal, context.getString(R.string.install_url)); - - MessageSender.send(context, OutgoingMessage.sms(resolved, message, subscriptionId), -1L, MessageSender.SendType.SMS, null, null); - - RecipientTable database = SignalDatabase.recipients(); - database.setHasSentInvite(recipient.getId()); - - return null; - }, v -> onSmsMessageSent.run()); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsUserAvatar.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsUserAvatar.java deleted file mode 100644 index 763e457612..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsUserAvatar.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.thoughtcrime.securesms.insights; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.widget.ImageView; - -import androidx.annotation.NonNull; - -import com.bumptech.glide.load.engine.DiskCacheStrategy; - -import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto; -import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; -import org.thoughtcrime.securesms.conversation.colors.AvatarColor; -import org.thoughtcrime.securesms.mms.GlideApp; - -class InsightsUserAvatar { - private final ProfileContactPhoto profileContactPhoto; - private final AvatarColor fallbackColor; - private final FallbackContactPhoto fallbackContactPhoto; - - InsightsUserAvatar(@NonNull ProfileContactPhoto profileContactPhoto, @NonNull AvatarColor fallbackColor, @NonNull FallbackContactPhoto fallbackContactPhoto) { - this.profileContactPhoto = profileContactPhoto; - this.fallbackColor = fallbackColor; - this.fallbackContactPhoto = fallbackContactPhoto; - } - - private Drawable fallbackDrawable(@NonNull Context context) { - return fallbackContactPhoto.asDrawable(context, fallbackColor); - } - - void load(ImageView into) { - GlideApp.with(into) - .load(profileContactPhoto) - .error(fallbackDrawable(into.getContext())) - .circleCrop() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(into); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java deleted file mode 100644 index d78759cd0b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.thoughtcrime.securesms.invites; - -import android.content.Context; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; - -import org.thoughtcrime.securesms.components.reminder.FirstInviteReminder; -import org.thoughtcrime.securesms.components.reminder.Reminder; -import org.thoughtcrime.securesms.components.reminder.SecondInviteReminder; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.recipients.LiveRecipient; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.signal.core.util.concurrent.SimpleTask; - -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; - -public final class InviteReminderModel { - - private static final int FIRST_INVITE_REMINDER_MESSAGE_THRESHOLD = 10; - private static final int SECOND_INVITE_REMINDER_MESSAGE_THRESHOLD = 500; - - private final Context context; - private final Repository repository; - private final AtomicReference reminderInfo = new AtomicReference<>(); - - public InviteReminderModel(@NonNull Context context, @NonNull Repository repository) { - this.context = context; - this.repository = repository; - } - - @MainThread - public void loadReminder(LiveRecipient liveRecipient, Runnable reminderCheckComplete) { - SimpleTask.run(() -> createReminderInfo(liveRecipient.resolve()), result -> { - reminderInfo.set(result); - reminderCheckComplete.run(); - }); - } - - @WorkerThread - private @NonNull ReminderInfo createReminderInfo(Recipient recipient) { - Recipient resolved = recipient.resolve(); - - if (resolved.isRegistered() || resolved.isGroup() || resolved.hasSeenSecondInviteReminder()) { - return new NoReminderInfo(); - } - - ThreadTable threadTable = SignalDatabase.threads(); - Long threadId = threadTable.getThreadIdFor(recipient.getId()); - - if (threadId != null) { - int conversationCount = SignalDatabase.messages().getInsecureMessageSentCount(threadId); - - if (conversationCount >= SECOND_INVITE_REMINDER_MESSAGE_THRESHOLD && !resolved.hasSeenSecondInviteReminder()) { - return new SecondInviteReminderInfo(context, resolved, repository, repository.getPercentOfInsecureMessages(conversationCount)); - } else if (conversationCount >= FIRST_INVITE_REMINDER_MESSAGE_THRESHOLD && !resolved.hasSeenFirstInviteReminder()) { - return new FirstInviteReminderInfo(resolved, repository, repository.getPercentOfInsecureMessages(conversationCount)); - } - } - return new NoReminderInfo(); - } - - public @NonNull Optional getReminder() { - ReminderInfo info = reminderInfo.get(); - if (info == null) return Optional.empty(); - else return Optional.ofNullable(info.reminder); - } - - public void dismissReminder() { - final ReminderInfo info = reminderInfo.getAndSet(null); - - SimpleTask.run(() -> { - info.dismiss(); - return null; - }, (v) -> {}); - } - - interface Repository { - void setHasSeenFirstInviteReminder(Recipient recipient); - void setHasSeenSecondInviteReminder(Recipient recipient); - int getPercentOfInsecureMessages(int insecureCount); - } - - private static abstract class ReminderInfo { - - private final Reminder reminder; - - ReminderInfo(Reminder reminder) { - this.reminder = reminder; - } - - @WorkerThread - void dismiss() { - } - } - - private static class NoReminderInfo extends ReminderInfo { - private NoReminderInfo() { - super(null); - } - } - - private class FirstInviteReminderInfo extends ReminderInfo { - - private final Repository repository; - private final Recipient recipient; - - private FirstInviteReminderInfo(@NonNull Recipient recipient, @NonNull Repository repository, int percentInsecure) { - super(new FirstInviteReminder(percentInsecure)); - - this.recipient = recipient; - this.repository = repository; - } - - @Override - @WorkerThread - void dismiss() { - repository.setHasSeenFirstInviteReminder(recipient); - } - } - - private static class SecondInviteReminderInfo extends ReminderInfo { - - private final Repository repository; - private final Recipient recipient; - - private SecondInviteReminderInfo(@NonNull Context context, @NonNull Recipient recipient, @NonNull Repository repository, int percentInsecure) { - super(new SecondInviteReminder(context, recipient, percentInsecure)); - - this.repository = repository; - this.recipient = recipient; - } - - @Override - @WorkerThread - void dismiss() { - repository.setHasSeenSecondInviteReminder(recipient); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java deleted file mode 100644 index 82e793b942..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.thoughtcrime.securesms.invites; - -import android.content.Context; - -import org.thoughtcrime.securesms.database.MessageTable; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.recipients.Recipient; - -public final class InviteReminderRepository implements InviteReminderModel.Repository { - - private final Context context; - - public InviteReminderRepository(Context context) { - this.context = context; - } - - @Override - public void setHasSeenFirstInviteReminder(Recipient recipient) { - RecipientTable recipientTable = SignalDatabase.recipients(); - recipientTable.setSeenFirstInviteReminder(recipient.getId()); - } - - @Override - public void setHasSeenSecondInviteReminder(Recipient recipient) { - RecipientTable recipientTable = SignalDatabase.recipients(); - recipientTable.setSeenSecondInviteReminder(recipient.getId()); - } - - @Override - public int getPercentOfInsecureMessages(int insecureCount) { - MessageTable messageTable = SignalDatabase.messages(); - int insecure = messageTable.getInsecureMessageCountForInsights(); - int secure = messageTable.getSecureMessageCountForInsights(); - - if (insecure + secure == 0) return 0; - return Math.round(100f * (insecureCount / (float) (insecure + secure))); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 20c482f62f..b564850518 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -91,7 +91,7 @@ public static void enqueue(@NonNull Context context, @NonNull JobManager jobMana } List compressionJobs = Stream.of(message.getAttachments()) - .map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, message.getSubscriptionId())) + .map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, -1)) .toList(); MmsSendJob sendJob = new MmsSendJob(messageId); @@ -139,7 +139,7 @@ public void onSend() throws MmsException, NoSuchMessageException, IOException { validateDestinations(message, pdu); final byte[] pduBytes = getPduBytes(pdu); - final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId()); + final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, -1); final MmsSendResult result = getSendResult(sendConf, pdu); database.markAsSent(messageId, false); @@ -231,7 +231,7 @@ private SendReq constructSendPdu(OutgoingMessage message) { SendReq req = new SendReq(); String lineNumber = getMyNumber(context); - MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId()); + MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(-1); List scaledAttachments = message.getAttachments(); if (!TextUtils.isEmpty(lineNumber)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 6eb05b2fdf..5fba6a5c8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -108,7 +108,7 @@ public void onRun() throws Exception { return; } - if (!recipient.isRegistered() || recipient.isForceSmsSelection()) { + if (!recipient.isRegistered()) { Log.w(TAG, "Not sending typing indicators to non-Signal recipients."); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index e30e324507..53524b5ac7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -414,8 +414,6 @@ else if (syncMessage.getCallEvent().isPresent()) { warn(String.valueOf(content.getTimestamp()), "Got unrecognized message!"); } - resetRecipientToPush(senderRecipient); - if (pending != null) { warn(content.getTimestamp(), "Pending retry was processed. Deleting."); ApplicationDependencies.getPendingRetryReceiptCache().delete(pending); @@ -2219,7 +2217,6 @@ private long handleSynchronizeSentStoryReply(@NonNull SentTranscriptMessage mess body, Collections.emptyList(), message.getTimestamp(), - -1, expiresInMillis, false, ThreadTable.DistributionTypes.DEFAULT, @@ -2342,7 +2339,6 @@ private void insertSentStoryMessage(@NonNull SentTranscriptMessage message, textStoryBody, pendingAttachments, sentAtTimestamp, - -1, 0, false, ThreadTable.DistributionTypes.DEFAULT, @@ -2442,7 +2438,6 @@ private long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage me message.getDataMessage().get().getBody().orElse(null), syncAttachments, message.getTimestamp(), - -1, TimeUnit.SECONDS.toMillis(message.getDataMessage().get().getExpiresInSeconds()), viewOnce, ThreadTable.DistributionTypes.DEFAULT, @@ -2657,7 +2652,6 @@ private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage mes new SlideDeck(), body, message.getTimestamp(), - -1, expiresInMillis, false, StoryType.NONE, @@ -3403,12 +3397,6 @@ private boolean shouldIgnore(@NonNull SignalServiceContent content, @NonNull Rec return false; } - private void resetRecipientToPush(@NonNull Recipient recipient) { - if (recipient.isForceSmsSelection()) { - SignalDatabase.recipients().setForceSmsSelection(recipient.getId(), false); - } - } - private void forceStickerDownloadIfNecessary(long messageId, List stickerAttachments) { if (stickerAttachments.isEmpty()) return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt index deadda5677..42e13f1de9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessorV2.kt @@ -284,12 +284,6 @@ open class MessageContentProcessorV2(private val context: Context) { null } } - - private fun resetRecipientToPush(recipient: Recipient) { - if (recipient.isForceSmsSelection) { - SignalDatabase.recipients.setForceSmsSelection(recipient.id, false) - } - } } /** @@ -425,8 +419,6 @@ open class MessageContentProcessorV2(private val context: Context) { } } - resetRecipientToPush(senderRecipient) - if (pending != null) { warn(envelope.timestamp, "Pending retry was processed. Deleting.") ApplicationDependencies.getPendingRetryReceiptCache().delete(pending) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt index 838af0a159..dcce6f0f83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt @@ -23,7 +23,6 @@ data class OutgoingMessage( val sentTimeMillis: Long, val body: String = "", val distributionType: Int = ThreadTable.DistributionTypes.DEFAULT, - val subscriptionId: Int = -1, val expiresIn: Long = 0L, val isViewOnce: Boolean = false, val outgoingQuote: QuoteModel? = null, @@ -66,7 +65,6 @@ data class OutgoingMessage( body: String? = "", attachments: List = emptyList(), timestamp: Long, - subscriptionId: Int = -1, expiresIn: Long = 0L, viewOnce: Boolean = false, distributionType: Int = ThreadTable.DistributionTypes.DEFAULT, @@ -89,7 +87,6 @@ data class OutgoingMessage( body = body ?: "", attachments = attachments, sentTimeMillis = timestamp, - subscriptionId = subscriptionId, expiresIn = expiresIn, isViewOnce = viewOnce, distributionType = distributionType, @@ -117,7 +114,6 @@ data class OutgoingMessage( slideDeck: SlideDeck, body: String? = "", timestamp: Long, - subscriptionId: Int = -1, expiresIn: Long = 0L, viewOnce: Boolean = false, storyType: StoryType = StoryType.NONE, @@ -131,7 +127,6 @@ data class OutgoingMessage( body = buildMessage(slideDeck, body ?: ""), attachments = slideDeck.asAttachments(), sentTimeMillis = timestamp, - subscriptionId = subscriptionId, expiresIn = expiresIn, isViewOnce = viewOnce, storyType = storyType, @@ -142,6 +137,8 @@ data class OutgoingMessage( sharedContacts = contacts ) + val subscriptionId = -1 + fun withExpiry(expiresIn: Long): OutgoingMessage { return copy(expiresIn = expiresIn) } @@ -172,12 +169,11 @@ data class OutgoingMessage( * A literal, insecure SMS message. */ @JvmStatic - fun sms(threadRecipient: Recipient, body: String, subscriptionId: Int): OutgoingMessage { + fun sms(threadRecipient: Recipient, body: String): OutgoingMessage { return OutgoingMessage( threadRecipient = threadRecipient, sentTimeMillis = System.currentTimeMillis(), body = body, - subscriptionId = subscriptionId, isSecure = false ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 02ef6c5e5a..cdf5022d9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -76,7 +76,6 @@ public void onReceive(final Context context, Intent intent) { long threadId; Recipient recipient = Recipient.resolved(recipientId); - int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds()); ParentStoryId parentStoryId = groupStoryId != Long.MIN_VALUE ? ParentStoryId.deserialize(groupStoryId) : null; @@ -86,7 +85,6 @@ public void onReceive(final Context context, Intent intent) { responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), - subscriptionId, expiresIn, false, 0, @@ -112,11 +110,6 @@ public void onReceive(final Context context, Intent intent) { threadId = MessageSender.send(context, reply, -1, MessageSender.SendType.SIGNAL, null, null); break; } - case UnsecuredSmsMessage: { - OutgoingMessage reply = OutgoingMessage.sms(recipient, responseText.toString(), subscriptionId); - threadId = MessageSender.send(context, reply, -1, MessageSender.SendType.SMS, null, null); - break; - } default: throw new AssertionError("Unknown Reply method"); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java index c755ddf1d4..ce797cd796 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java @@ -1,26 +1,19 @@ package org.thoughtcrime.securesms.notifications; -import android.content.Context; - import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; public enum ReplyMethod { GroupMessage, - SecureMessage, - UnsecuredSmsMessage; + SecureMessage; - public static @NonNull ReplyMethod forRecipient(Context context, Recipient recipient) { + public static @NonNull ReplyMethod forRecipient(Recipient recipient) { if (recipient.isGroup()) { return ReplyMethod.GroupMessage; - } else if (SignalStore.account().isRegistered() && recipient.getRegistered() == RecipientTable.RegisteredState.REGISTERED && !recipient.isForceSmsSelection()) { - return ReplyMethod.SecureMessage; } else { - return ReplyMethod.UnsecuredSmsMessage; + return ReplyMethod.SecureMessage; } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index 61ecf33c55..0d9f2f18c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -123,7 +123,7 @@ sealed class NotificationBuilder(protected val context: Context) { } } - addActions(ReplyMethod.forRecipient(context, conversation.recipient), conversation) + addActions(ReplyMethod.forRecipient(conversation.recipient), conversation) } } @@ -504,6 +504,5 @@ private fun ReplyMethod.toLongDescription(): Int { return when (this) { ReplyMethod.GroupMessage -> R.string.MessageNotifier_reply ReplyMethod.SecureMessage -> R.string.MessageNotifier_signal_message - ReplyMethod.UnsecuredSmsMessage -> R.string.MessageNotifier_unsecured_sms } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index f35bf4cb23..233be951c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -104,7 +104,6 @@ public class Recipient { private final VibrateState callVibrate; private final Uri messageRingtone; private final Uri callRingtone; - private final Optional defaultSubscriptionId; private final int expireMessages; private final RegisteredState registered; private final byte[] profileKey; @@ -121,9 +120,7 @@ public class Recipient { private final long lastProfileFetch; private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; - private final boolean forceSmsSelection; private final RecipientRecord.Capabilities capabilities; - private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; private final MentionSetting mentionSetting; private final ChatWallpaper wallpaper; @@ -392,8 +389,6 @@ public static boolean isSelfSet() { this.callVibrate = VibrateState.DEFAULT; this.messageRingtone = null; this.callRingtone = null; - this.insightsBannerTier = InsightsBannerTier.TIER_TWO; - this.defaultSubscriptionId = Optional.empty(); this.expireMessages = 0; this.registered = RegisteredState.UNKNOWN; this.profileKey = null; @@ -410,7 +405,6 @@ public static boolean isSelfSet() { this.lastProfileFetch = 0; this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; - this.forceSmsSelection = false; this.capabilities = RecipientRecord.Capabilities.UNKNOWN; this.storageId = null; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; @@ -450,8 +444,6 @@ public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boo this.callVibrate = details.callVibrateState; this.messageRingtone = details.messageRingtone; this.callRingtone = details.callRingtone; - this.insightsBannerTier = details.insightsBannerTier; - this.defaultSubscriptionId = details.defaultSubscriptionId; this.expireMessages = details.expireMessages; this.registered = details.registered; this.profileKey = details.profileKey; @@ -468,7 +460,6 @@ public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boo this.lastProfileFetch = details.lastProfileFetch; this.notificationChannel = details.notificationChannel; this.unidentifiedAccessMode = details.unidentifiedAccessMode; - this.forceSmsSelection = details.forceSmsSelection; this.capabilities = details.capabilities; this.storageId = details.storageId; this.mentionSetting = details.mentionSetting; @@ -840,10 +831,6 @@ public boolean hasViewedStory() { return requireSmsAddress(); } - public Optional getDefaultSubscriptionId() { - return defaultSubscriptionId; - } - public @NonNull ProfileName getProfileName() { return signalProfileName; } @@ -1029,14 +1016,6 @@ public int getExpiresInSeconds() { return expireMessages; } - public boolean hasSeenFirstInviteReminder() { - return insightsBannerTier.seen(InsightsBannerTier.TIER_ONE); - } - - public boolean hasSeenSecondInviteReminder() { - return insightsBannerTier.seen(InsightsBannerTier.TIER_TWO); - } - public @NonNull RegisteredState getRegistered() { if (isPushGroup() || isDistributionList()) { return RegisteredState.REGISTERED; @@ -1063,10 +1042,6 @@ public boolean isUnregistered() { return !NotificationChannels.supported() ? null : notificationChannel; } - public boolean isForceSmsSelection() { - return forceSmsSelection; - } - public @NonNull Capability getStoriesCapability() { return capabilities.getStoriesCapability(); } @@ -1353,7 +1328,6 @@ public boolean hasSameContent(@NonNull Recipient other) { Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) && profileSharing == other.profileSharing && isHidden == other.isHidden && - forceSmsSelection == other.forceSmsSelection && Objects.equals(aci, other.aci) && Objects.equals(username, other.username) && Objects.equals(e164, other.e164) && @@ -1365,7 +1339,6 @@ public boolean hasSameContent(@NonNull Recipient other) { callVibrate == other.callVibrate && Objects.equals(messageRingtone, other.messageRingtone) && Objects.equals(callRingtone, other.callRingtone) && - Objects.equals(defaultSubscriptionId, other.defaultSubscriptionId) && registered == other.registered && Arrays.equals(profileKey, other.profileKey) && Objects.equals(expiringProfileKeyCredential, other.expiringProfileKeyCredential) && @@ -1378,7 +1351,6 @@ public boolean hasSameContent(@NonNull Recipient other) { Objects.equals(profileAvatar, other.profileAvatar) && Objects.equals(notificationChannel, other.notificationChannel) && unidentifiedAccessMode == other.unidentifiedAccessMode && - insightsBannerTier == other.insightsBannerTier && Arrays.equals(storageId, other.storageId) && mentionSetting == other.mentionSetting && Objects.equals(wallpaper, other.wallpaper) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 26b60b426c..a9dfb8ba80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -58,7 +58,6 @@ public class RecipientDetails { final int expireMessages; final List participantIds; final ProfileName profileName; - final Optional defaultSubscriptionId; final RegisteredState registered; final byte[] profileKey; final ExpiringProfileKeyCredential expiringProfileKeyCredential; @@ -72,9 +71,7 @@ public class RecipientDetails { final boolean isSelf; final String notificationChannel; final UnidentifiedAccessMode unidentifiedAccessMode; - final boolean forceSmsSelection; final RecipientRecord.Capabilities capabilities; - final InsightsBannerTier insightsBannerTier; final byte[] storageId; final MentionSetting mentionSetting; final ChatWallpaper wallpaper; @@ -125,7 +122,6 @@ public RecipientDetails(@Nullable String groupName, this.participantIds = participantIds == null ? new LinkedList<>() : participantIds; this.isActiveGroup = isActiveGroup; this.profileName = record.getProfileName(); - this.defaultSubscriptionId = record.getDefaultSubscriptionId(); this.registered = registeredState; this.profileKey = record.getProfileKey(); this.expiringProfileKeyCredential = record.getExpiringProfileKeyCredential(); @@ -138,9 +134,7 @@ public RecipientDetails(@Nullable String groupName, this.isSelf = isSelf; this.notificationChannel = record.getNotificationChannel(); this.unidentifiedAccessMode = record.getUnidentifiedAccessMode(); - this.forceSmsSelection = record.isForceSmsSelection(); this.capabilities = record.getCapabilities(); - this.insightsBannerTier = record.getInsightsBannerTier(); this.storageId = record.getStorageId(); this.mentionSetting = record.getMentionSetting(); this.wallpaper = record.getWallpaper(); @@ -181,8 +175,6 @@ private RecipientDetails() { this.expireMessages = 0; this.participantIds = new LinkedList<>(); this.profileName = ProfileName.EMPTY; - this.insightsBannerTier = InsightsBannerTier.TIER_TWO; - this.defaultSubscriptionId = Optional.empty(); this.registered = RegisteredState.UNKNOWN; this.profileKey = null; this.expiringProfileKeyCredential = null; @@ -195,7 +187,6 @@ private RecipientDetails() { this.isSelf = false; this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; - this.forceSmsSelection = false; this.groupName = null; this.capabilities = RecipientRecord.Capabilities.UNKNOWN; this.storageId = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index be60ca3e85..d5e2621791 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -297,7 +297,6 @@ public static boolean isLegacyProfileSharingAccepted(@NonNull Recipient threadRe threadRecipient.isProfileSharing() || threadRecipient.isSystemContact() || !threadRecipient.isRegistered() || - threadRecipient.isForceSmsSelection() || threadRecipient.isHidden(); } @@ -346,7 +345,6 @@ public static boolean isMessageRequestAccepted(@Nullable Long threadId, @Nullabl threadRecipient.isSelf() || threadRecipient.isProfileSharing() || threadRecipient.isSystemContact() || - threadRecipient.isForceSmsSelection() || !threadRecipient.isRegistered() || (!threadRecipient.isHidden() && ( hasSentMessageInThread(threadId) || diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java index 8233a4ed20..445b9f0f08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -46,11 +46,10 @@ protected void onHandleIntent(Intent intent) { number = URLDecoder.decode(number); } - Recipient recipient = Recipient.external(this, number); - int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1); + Recipient recipient = Recipient.external(this, number); if (!TextUtils.isEmpty(content)) { - MessageSender.send(this, OutgoingMessage.sms(recipient, content, subscriptionId), -1, MessageSender.SendType.SIGNAL, null, null); + MessageSender.send(this, OutgoingMessage.sms(recipient, content), -1, MessageSender.SendType.SIGNAL, null, null); } } catch (URISyntaxException e) { Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java index ac2b1439b1..bb4742d515 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java @@ -108,9 +108,7 @@ public static MultiShareSendResultCollection sendSync(@NonNull MultiShareArgs mu long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient); List mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions()); - MessageSendType sendType = resolveTransportOption(context, recipient); - boolean forceSms = recipient.isForceSmsSelection() && sendType.usesSmsTransport(); - int subscriptionId = sendType.getSimSubscriptionIdOr(-1); + MessageSendType sendType = MessageSendType.SignalMessageSendType.INSTANCE; long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds()); List contacts = multiShareArgs.getSharedContacts(); boolean needsSplit = !sendType.usesSmsTransport() && @@ -139,10 +137,8 @@ public static MultiShareSendResultCollection sendSync(@NonNull MultiShareArgs mu slideDeck, sendType, threadId, - forceSms, expiresIn, multiShareArgs.isViewOnce(), - subscriptionId, mentions, recipientSearchKey.isStory(), sentTimestamp, @@ -154,7 +150,7 @@ public static MultiShareSendResultCollection sendSync(@NonNull MultiShareArgs mu } else if (recipientSearchKey.isStory()) { results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.INVALID_SHARE_TO_STORY)); } else { - sendTextMessage(context, multiShareArgs, recipient, threadId, forceSms, expiresIn, subscriptionId); + sendTextMessage(context, multiShareArgs, recipient, threadId, expiresIn); results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.SUCCESS)); } @@ -179,39 +175,14 @@ public static MultiShareSendResultCollection sendSync(@NonNull MultiShareArgs mu return new MultiShareSendResultCollection(results); } - public static @NonNull MessageSendType getWorstTransportOption(@NonNull Context context, @NonNull Set recipientSearchKeys) { - for (ContactSearchKey.RecipientSearchKey recipientSearchKey : recipientSearchKeys) { - MessageSendType type = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory()); - if (type.usesSmsTransport()) { - return type; - } - } - - return MessageSendType.SignalMessageSendType.INSTANCE; - } - - private static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) { - return resolveTransportOption(context, !recipient.isDistributionList() && (recipient.isForceSmsSelection() || !recipient.isRegistered())); - } - - public static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, boolean forceSms) { - if (forceSms && SignalStore.misc().getSmsExportPhase().allowSmsFeatures()) { - return MessageSendType.getFirstForTransport(context, false, MessageSendType.TransportType.SMS); - } else { - return MessageSendType.SignalMessageSendType.INSTANCE; - } - } - private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context context, @NonNull MultiShareArgs multiShareArgs, @NonNull Recipient recipient, @NonNull SlideDeck slideDeck, @NonNull MessageSendType sendType, long threadId, - boolean forceSms, long expiresIn, boolean isViewOnce, - int subscriptionId, @NonNull List validatedMentions, boolean isStory, @NonNull MultiShareTimestampProvider sentTimestamps, @@ -221,7 +192,7 @@ private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context conte @NonNull List contacts) { String body = multiShareArgs.getDraftText(); - if (sendType.usesSignalTransport() && !forceSms && body != null) { + if (sendType.usesSignalTransport() && body != null) { MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize); body = splitMessage.getBody(); @@ -249,7 +220,6 @@ private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context conte new SlideDeck(), body, sentTimestamps.getMillis(0), - subscriptionId, 0L, false, storyType.toTextStoryType(), @@ -289,7 +259,6 @@ private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context conte singletonDeck, body, sentTimestamps.getMillis(i), - subscriptionId, 0L, false, storyType, @@ -307,7 +276,6 @@ private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context conte slideDeck, body, sentTimestamps.getMillis(0), - subscriptionId, expiresIn, isViewOnce, StoryType.NONE, @@ -322,7 +290,7 @@ private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context conte if (isStory) { storiesToBatchSend.addAll(outgoingMessages); - } else if (shouldSendAsPush(recipient, forceSms)) { + } else if (shouldSendAsPush(recipient)) { for (final OutgoingMessage outgoingMessage : outgoingMessages) { MessageSender.send(context, outgoingMessage.makeSecure(), threadId, SendType.SIGNAL, null, null); } @@ -399,20 +367,18 @@ private static void sendTextMessage(@NonNull Context context, @NonNull MultiShareArgs multiShareArgs, @NonNull Recipient recipient, long threadId, - boolean forceSms, - long expiresIn, - int subscriptionId) + long expiresIn) { String body = multiShareArgs.getDraftText() == null ? "" : multiShareArgs.getDraftText(); OutgoingMessage outgoingMessage; - if (shouldSendAsPush(recipient, forceSms)) { + if (shouldSendAsPush(recipient)) { outgoingMessage = OutgoingMessage.text(recipient, body, expiresIn, System.currentTimeMillis(), multiShareArgs.getBodyRanges()); } else { - outgoingMessage = OutgoingMessage.sms(recipient, body, subscriptionId); + outgoingMessage = OutgoingMessage.sms(recipient, body); } - MessageSender.send(context, outgoingMessage, threadId, forceSms ? SendType.SMS : SendType.SIGNAL, null, null); + MessageSender.send(context, outgoingMessage, threadId, SendType.SIGNAL, null, null); } private static @NonNull OutgoingMessage generateTextStory(@NonNull Context context, @@ -458,10 +424,10 @@ private static void sendTextMessage(@NonNull Context context, return trimmed.replace(linkPreview.getUrl(), "").trim(); } - private static boolean shouldSendAsPush(@NonNull Recipient recipient, boolean forceSms) { + private static boolean shouldSendAsPush(@NonNull Recipient recipient) { return recipient.isDistributionList() || recipient.isServiceIdOnly() || - (recipient.isRegistered() && !forceSms); + recipient.isRegistered(); } private static @NonNull SlideDeck buildSlideDeck(@NonNull Context context, @NonNull MultiShareArgs multiShareArgs) throws SlideNotFoundException { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/interstitial/ShareInterstitialActivity.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/interstitial/ShareInterstitialActivity.java index 08345e295e..86df41c31a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/interstitial/ShareInterstitialActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/interstitial/ShareInterstitialActivity.java @@ -93,7 +93,7 @@ private void initializeViewModels(@NonNull MultiShareArgs args) { return false; } - return !recipient.isRegistered() || recipient.isForceSmsSelection(); + return !recipient.isRegistered(); }); if (hasSms) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt index 09e383c5f4..77b175252d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.ConversationIntents +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFullScreenDialogFragment @@ -271,7 +272,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C val intent = share( this, - MultiShareSender.getWorstTransportOption(this, multiShareArgs.recipientSearchKeys), + MessageSendType.SignalMessageSendType, media, multiShareArgs.recipientSearchKeys.toList(), multiShareArgs.draftText, diff --git a/app/src/main/res/layout/insights_dashboard.xml b/app/src/main/res/layout/insights_dashboard.xml deleted file mode 100644 index 515c4131c4..0000000000 --- a/app/src/main/res/layout/insights_dashboard.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/insights_dashboard_adapter_item.xml b/app/src/main/res/layout/insights_dashboard_adapter_item.xml deleted file mode 100644 index 95b7bc57d6..0000000000 --- a/app/src/main/res/layout/insights_dashboard_adapter_item.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/insights_modal.xml b/app/src/main/res/layout/insights_modal.xml deleted file mode 100644 index 55476fb53d..0000000000 --- a/app/src/main/res/layout/insights_modal.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -