diff --git a/.tx/config b/.tx/config deleted file mode 100644 index 9661334afd..0000000000 --- a/.tx/config +++ /dev/null @@ -1,10 +0,0 @@ -[main] -host = https://www.transifex.com -lang_map = da_DK:da-rDK,he:iw,id:in,kn_IN:kn-rIN,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW - -[signal-android.master] -file_filter = app/src/main/res/values-/strings.xml -source_file = app/src/main/res/values/strings.xml -source_lang = en -type = ANDROID - diff --git a/README.md b/README.md index 5e41159c45..e3f06a301d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,39 @@ Please search for any [existing issues](https://github.com/oxen-io/session-andro Build instructions can be found in [BUILDING.md](BUILDING.md). +## Verifing signatures + +Get Kee's key and import it: + +``` +wget https://raw.githubusercontent.com/oxen-io/oxen-core/master/utils/gpg_keys/KeeJef.asc +gpg --import KeeJef.asc +``` + +Get the signed hash for this release, the SESSION_VERSION needs to be updated for the release you want to verify + +``` +export SESSION_VERSION=1.10.4 +wget https://github.com/oxen-io/session-android/releases/download/$SESSION_VERSION/signatures.asc +``` + +Verify the signature of the hashes of the files + +``` +gpg --verify signatures.asc 2>&1 |grep "Good signature from" +``` + +The command above should print "`Good signature from "Kee Jefferys...`" +If it does, the hashes are valid but we still have to make the sure the signed hashes matches the downloaded files. + +Make sure the two commands below returns the same hash. +If they do, files are valid. + +``` +sha256sum session-$SESSION_VERSION-universal.apk +grep universal.apk signatures.asc +``` + ## License Copyright 2011 Whisper Systems diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 972ec30be1..3d223e42c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -172,9 +172,6 @@ - - - - - - - - - - - - - allContacts = ContactUtilities.getAllContacts(this); + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(this); + LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); + for (Recipient recipient : allContacts) { + if (recipient.isGroupRecipient()) { continue; } + String sessionID = recipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact == null) { + contact = new Contact(sessionID); + String name = userDB.getDisplayName(sessionID); + contact.setName(name); + contact.setProfilePictureURL(recipient.getProfileAvatar()); + contact.setProfilePictureEncryptionKey(recipient.getProfileKey()); + contact.setTrusted(true); + } + contactDB.setContact(contact); + } + } + if (poller != null) { poller.setCaughtUp(false); } startPollingIfNeeded(); - OpenGroupManager.INSTANCE.setAllCaughtUp(false); OpenGroupManager.INSTANCE.startPolling(); } @@ -210,7 +220,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc Log.i(TAG, "App is no longer visible."); KeyCachingService.onAppBackgrounded(this); messageNotifier.setVisibleThread(-1); - // Loki if (poller != null) { poller.stopIfNeeded(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index b556d20f55..85cf9332bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -18,8 +18,11 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.annimon.stream.Stream; import com.bumptech.glide.load.engine.DiskCacheStrategy; + +import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; @@ -197,7 +200,14 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) { quoteeDisplayName = TextSecurePreferences.getProfileName(getContext()); } else { - quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(senderHexEncodedPublicKey); + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(getContext()); + Contact contact = contactDB.getContactWithSessionID(senderHexEncodedPublicKey); + if (contact != null) { + Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR; + quoteeDisplayName = contact.displayName(context); + } else { + quoteeDisplayName = senderHexEncodedPublicKey; + } } authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index ddad4b628a..c1f46a6bcf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -62,7 +62,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -103,6 +102,7 @@ import org.session.libsession.utilities.recipients.RecipientModifiedListener; import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; +import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; @@ -157,8 +157,6 @@ import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity; import org.thoughtcrime.securesms.loki.activities.HomeActivity; import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity; import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker; -import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; -import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; @@ -212,14 +210,14 @@ import network.loki.messenger.R; */ @SuppressLint("StaticFieldLeak") public class ConversationActivity extends PassphraseRequiredActionBarActivity - implements ConversationFragment.ConversationFragmentListener, - AttachmentManager.AttachmentListener, - RecipientModifiedListener, - OnKeyboardShownListener, - InputPanel.Listener, - InputPanel.MediaListener, - ComposeText.CursorPositionChangedListener, - ConversationSearchBottomBar.EventListener + implements ConversationFragment.ConversationFragmentListener, + AttachmentManager.AttachmentListener, + RecipientModifiedListener, + OnKeyboardShownListener, + InputPanel.Listener, + InputPanel.MediaListener, + ComposeText.CursorPositionChangedListener, + ConversationSearchBottomBar.EventListener { private static final String TAG = ConversationActivity.class.getSimpleName(); @@ -234,11 +232,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public static final String LAST_SEEN_EXTRA = "last_seen"; public static final String STARTING_POSITION_EXTRA = "starting_position"; -// private static final int PICK_GALLERY = 1; + // private static final int PICK_GALLERY = 1; private static final int PICK_DOCUMENT = 2; private static final int PICK_AUDIO = 3; private static final int PICK_CONTACT = 4; -// private static final int GET_CONTACT_DETAILS = 5; + // private static final int GET_CONTACT_DETAILS = 5; // private static final int GROUP_EDIT = 6; private static final int TAKE_PHOTO = 7; private static final int ADD_CONTACT = 8; @@ -418,7 +416,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i(TAG, "onNewIntent()"); - + if (isFinishing()) { Log.w(TAG, "Activity is finishing..."); return; @@ -513,90 +511,89 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity super.onActivityResult(reqCode, resultCode, data); if ((data == null && reqCode != TAKE_PHOTO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) + (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) { updateLinkPreviewState(); return; } switch (reqCode) { - case PICK_DOCUMENT: - setMedia(data.getData(), MediaType.DOCUMENT); - break; - case PICK_AUDIO: - setMedia(data.getData(), MediaType.AUDIO); - break; - case TAKE_PHOTO: - if (attachmentManager.getCaptureUri() != null) { - setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); - } - break; - case ADD_CONTACT: - recipient = Recipient.from(this, recipient.getAddress(), true); - recipient.addListener(this); - fragment.reloadList(); - break; + case PICK_DOCUMENT: + setMedia(data.getData(), MediaType.DOCUMENT); + break; + case PICK_AUDIO: + setMedia(data.getData(), MediaType.AUDIO); + break; + case TAKE_PHOTO: + if (attachmentManager.getCaptureUri() != null) { + setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); + } + break; + case ADD_CONTACT: + recipient = Recipient.from(this, recipient.getAddress(), true); + recipient.addListener(this); + fragment.reloadList(); + break; /* case PICK_LOCATION: SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); attachmentManager.setLocation(place, getCurrentMediaConstraints()); break; */ - case PICK_GIF: - setMedia(data.getData(), - MediaType.GIF, - data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), - data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); - break; - case SMS_DEFAULT: - initializeSecurity(true, isDefaultSms); - break; - case MEDIA_SENDER: - long expiresIn = recipient.getExpireMessages() * 1000L; - int subscriptionId = -1; - boolean initiating = threadId == -1; - String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); - SlideDeck slideDeck = new SlideDeck(); + case PICK_GIF: + setMedia(data.getData(), + MediaType.GIF, + data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), + data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); + break; + case SMS_DEFAULT: + initializeSecurity(true, isDefaultSms); + break; + case MEDIA_SENDER: + long expiresIn = recipient.getExpireMessages() * 1000L; + int subscriptionId = -1; + boolean initiating = threadId == -1; + String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); + SlideDeck slideDeck = new SlideDeck(); - List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); + List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); - for (Media mediaItem : mediaList) { - if (MediaUtil.isVideoType(mediaItem.getMimeType())) { - slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); - } else if (MediaUtil.isGif(mediaItem.getMimeType())) { - slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { - slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else { - Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); + for (Media mediaItem : mediaList) { + if (MediaUtil.isVideoType(mediaItem.getMimeType())) { + slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); + } else if (MediaUtil.isGif(mediaItem.getMimeType())) { + slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); + } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { + slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); + } else { + Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); + } } - } - final Context context = ConversationActivity.this.getApplicationContext(); + final Context context = ConversationActivity.this.getApplicationContext(); - sendMediaMessage(message, - slideDeck, - inputPanel.getQuote().orNull(), - Optional.absent(), - initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - Stream.of(slideDeck.getSlides()) - .map(Slide::getUri) - .withoutNulls() - .filter(BlobProvider::isAuthority) - .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); - }); - } - }); - - break; - case INVITE_CONTACTS: - if (data.getExtras() == null || !data.hasExtra(SelectContactsActivity.Companion.getSelectedContactsKey())) return; - String[] selectedContacts = data.getExtras().getStringArray(SelectContactsActivity.Companion.getSelectedContactsKey()); - sendOpenGroupInvitations(selectedContacts); - break; + sendMediaMessage(message, + slideDeck, + inputPanel.getQuote().orNull(), + Optional.absent(), + initiating).addListener(new AssertedSuccessListener() { + @Override + public void onSuccess(Void result) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + Stream.of(slideDeck.getSlides()) + .map(Slide::getUri) + .withoutNulls() + .filter(BlobProvider::isAuthority) + .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); + }); + } + }); + break; + case INVITE_CONTACTS: + if (data.getExtras() == null || !data.hasExtra(SelectContactsActivity.Companion.getSelectedContactsKey())) return; + String[] selectedContacts = data.getExtras().getStringArray(SelectContactsActivity.Companion.getSelectedContactsKey()); + sendOpenGroupInvitations(selectedContacts); + break; } } @@ -676,14 +673,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (isSingleConversation() && getRecipient().getContactUri() == null) { inflater.inflate(R.menu.conversation_add_to_contacts, menu); } - - if (recipient != null && recipient.isLocalNumber()) { if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); else menu.findItem(R.id.menu_call_insecure).setVisible(false); - MenuItem muteItem = menu.findItem(R.id.menu_mute_notifications); - if (muteItem != null) { muteItem.setVisible(false); } @@ -751,26 +744,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity switch (item.getItemId()) { // case R.id.menu_call_secure: handleDial(getRecipient(), true); return true; // case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true; - case R.id.menu_unblock: handleUnblock(); return true; - case R.id.menu_block: handleBlock(); return true; - case R.id.menu_copy_session_id: handleCopySessionID(); return true; - case R.id.menu_view_media: handleViewMedia(); return true; - case R.id.menu_add_shortcut: handleAddShortcut(); return true; - case R.id.menu_search: handleSearch(); return true; + case R.id.menu_unblock: handleUnblock(); return true; + case R.id.menu_block: handleBlock(); return true; + case R.id.menu_copy_session_id: handleCopySessionID(); return true; + case R.id.menu_view_media: handleViewMedia(); return true; + case R.id.menu_add_shortcut: handleAddShortcut(); return true; + case R.id.menu_search: handleSearch(); return true; // case R.id.menu_add_to_contacts: handleAddToContacts(); return true; // case R.id.menu_reset_secure_session: handleResetSecureSession(); return true; // case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; - case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; - case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; - case R.id.menu_edit_group: handleEditPushGroup(); return true; - case R.id.menu_leave: handleLeavePushGroup(); return true; - case R.id.menu_mute_notifications: handleMuteNotifications(); return true; - case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; + case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; + case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; + case R.id.menu_edit_group: handleEditPushGroup(); return true; + case R.id.menu_leave: handleLeavePushGroup(); return true; + case R.id.menu_mute_notifications: handleMuteNotifications(); return true; + case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; // case R.id.menu_conversation_settings: handleConversationSettings(); return true; - case R.id.menu_expiring_messages_off: - case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; - case R.id.menu_invite_to_open_group: handleInviteToOpenGroup(); return true; - case android.R.id.home: handleReturnToConversationList(); return true; + case R.id.menu_expiring_messages_off: + case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; + case R.id.menu_invite_to_open_group: handleInviteToOpenGroup(); return true; + case android.R.id.home: handleReturnToConversationList(); return true; } return false; @@ -841,7 +834,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, until); + .setMuted(recipient, until); return null; } @@ -856,7 +849,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, 0); + .setMuted(recipient, 0); return null; } @@ -868,20 +861,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, false); + .setTitle(titleRes) + .setMessage(bodyRes) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + DatabaseFactory.getRecipientDatabase(ConversationActivity.this) + .setBlocked(recipient, false); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }).show(); } @TargetApi(Build.VERSION_CODES.KITKAT) @@ -951,7 +944,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (icon == null) { icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut - : R.mipmap.ic_person_shortcut); + : R.mipmap.ic_person_shortcut); } return icon; @@ -961,14 +954,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected void onPostExecute(IconCompat icon) { Context context = getApplicationContext(); String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.toShortString()); + .or(Optional.fromNullable(recipient.getProfileName())) + .or(recipient.toShortString()); ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis()) - .setShortLabel(name) - .setIcon(icon) - .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) - .build(); + .setShortLabel(name) + .setIcon(icon) + .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) + .build(); if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show(); @@ -984,7 +977,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleLeavePushGroup() { if (getRecipient() == null) { Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG).show(); return; } @@ -1052,7 +1045,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.BROADCAST); + .setDistributionType(threadId, DistributionTypes.BROADCAST); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1068,7 +1061,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.CONVERSATION); + .setDistributionType(threadId, DistributionTypes.CONVERSATION); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1100,7 +1093,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity /* Loki - We don't support SMS if (!isSecureText && !isPushGroupConversation()) sendButton.disableTransport(Type.TEXTSECURE); if (recipient.isPushGroupRecipient()) sendButton.disableTransport(Type.SMS); - if (!recipient.isPushGroupRecipient() && recipient.isForceSmsSelection()) { sendButton.setDefaultTransport(Type.SMS); } else { @@ -1425,35 +1417,35 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.i(TAG, "Selected: " + type); switch (type) { - case AttachmentTypeSelector.ADD_GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; - case AttachmentTypeSelector.ADD_DOCUMENT: - AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; - case AttachmentTypeSelector.ADD_SOUND: - AttachmentManager.selectAudio(this, PICK_AUDIO); break; - case AttachmentTypeSelector.ADD_CONTACT_INFO: - AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; - case AttachmentTypeSelector.ADD_LOCATION: - AttachmentManager.selectLocation(this, PICK_LOCATION); break; - case AttachmentTypeSelector.TAKE_PHOTO: - attachmentManager.capturePhoto(this, TAKE_PHOTO); break; - case AttachmentTypeSelector.ADD_GIF: - boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); - if (!hasSeenGIFMetaDataWarning) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Search GIFs?"); - builder.setMessage("You will not have full metadata protection when sending GIFs."); - builder.setPositiveButton("OK", (dialog, which) -> { + case AttachmentTypeSelector.ADD_GALLERY: + AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; + case AttachmentTypeSelector.ADD_DOCUMENT: + AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; + case AttachmentTypeSelector.ADD_SOUND: + AttachmentManager.selectAudio(this, PICK_AUDIO); break; + case AttachmentTypeSelector.ADD_CONTACT_INFO: + AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; + case AttachmentTypeSelector.ADD_LOCATION: + AttachmentManager.selectLocation(this, PICK_LOCATION); break; + case AttachmentTypeSelector.TAKE_PHOTO: + attachmentManager.capturePhoto(this, TAKE_PHOTO); break; + case AttachmentTypeSelector.ADD_GIF: + boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); + if (!hasSeenGIFMetaDataWarning) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Search GIFs?"); + builder.setMessage("You will not have full metadata protection when sending GIFs."); + builder.setPositiveButton("OK", (dialog, which) -> { + AttachmentManager.selectGif(this, PICK_GIF); + dialog.dismiss(); + }); + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); + builder.create().show(); + TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); + } else { AttachmentManager.selectGif(this, PICK_GIF); - dialog.dismiss(); - }); - builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); - builder.create().show(); - TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); - } else { - AttachmentManager.selectGif(this, PICK_GIF); - } - break; + } + break; } } @@ -1548,8 +1540,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity draftDatabase.insertDrafts(threadId, drafts); threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), - drafts.getUriSnippet(), - System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); + drafts.getUriSnippet(), + System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); } else if (threadId > 0) { threadDatabase.update(threadId, false); } @@ -1652,10 +1644,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date()); String filename = String.format("signal-%s.txt", timestamp); Uri textUri = BlobProvider.getInstance() - .forData(textData) - .withMimeType(MediaTypes.LONG_TEXT) - .withFileName(filename) - .createForSingleSessionInMemory(); + .forData(textData) + .withMimeType(MediaTypes.LONG_TEXT) + .withFileName(filename) + .createForSingleSessionInMemory(); textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length)); } @@ -1733,10 +1725,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; boolean isMediaMessage = attachmentManager.isAttachmentPresent() || // recipient.isGroupRecipient() || - inputPanel.getQuote().isPresent() || - linkPreviewViewModel.hasLinkPreview() || - LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages - needsSplit; + inputPanel.getQuote().isPresent() || + linkPreviewViewModel.hasLinkPreview() || + LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages + needsSplit; if (isMediaMessage) { sendMediaMessage(initiating); @@ -1757,7 +1749,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void sendMediaMessage(boolean initiating) - throws InvalidMessageException + throws InvalidMessageException { Log.i(TAG, "Sending media message..."); sendMediaMessage(getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), linkPreviewViewModel.getActiveLinkPreview(), initiating); @@ -1817,7 +1809,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void sendTextMessage(final boolean initiating) - throws InvalidMessageException + throws InvalidMessageException { final Context context = getApplicationContext(); final String messageBody = getMessage(); @@ -1895,10 +1887,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onRecorderPermissionRequired() { Permissions.with(this) - .request(Manifest.permission.RECORD_AUDIO) - .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) - .execute(); + .request(Manifest.permission.RECORD_AUDIO) + .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) + .execute(); } @Override @@ -2057,16 +2049,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onClick(View v) { Permissions.with(ConversationActivity.this) - .request(Manifest.permission.CAMERA) - .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) - .onAllGranted(() -> { - composeText.clearFocus(); - startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); - overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); - }) - .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) - .execute(); + .request(Manifest.permission.CAMERA) + .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) + .onAllGranted(() -> { + composeText.clearFocus(); + startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); + overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); + }) + .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) + .execute(); } } @@ -2176,7 +2168,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } if (text.length() > 0) { if (currentMentionStartIndex > text.length()) { - resetMentions(); // Should never occur + resetMentions(); // Should never occur } int lastCharacterIndex = text.length() - 1; char lastCharacter = text.charAt(lastCharacterIndex); @@ -2184,11 +2176,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (lastCharacterIndex > 0) { secondToLastCharacter = text.charAt(lastCharacterIndex - 1); } - String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(ConversationActivity.this); - LokiThreadDatabase threadDatabase = DatabaseFactory.getLokiThreadDatabase(ConversationActivity.this); - LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(ConversationActivity.this); if (lastCharacter == '@' && Character.isWhitespace(secondToLastCharacter)) { - List mentionCandidates = MentionsManager.shared.getMentionCandidates("", threadId); + List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates("", threadId, recipient.isOpenGroupRecipient()); currentMentionStartIndex = lastCharacterIndex; mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); mentionCandidateSelectionView.show(mentionCandidates, threadId); @@ -2199,7 +2188,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { if (currentMentionStartIndex != -1) { String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @ - List mentionCandidates = MentionsManager.shared.getMentionCandidates(query, threadId); + List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates(query, threadId, recipient.isOpenGroupRecipient()); mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); mentionCandidateSelectionView.show(mentionCandidates, threadId); } @@ -2243,12 +2232,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - body, - slideDeck, - recipient, - threadId); + messageRecord.getDateSent(), + author, + body, + slideDeck, + recipient, + threadId); } else if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); @@ -2259,26 +2248,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - slideDeck, - recipient, - threadId); + messageRecord.getDateSent(), + author, + messageRecord.getBody(), + slideDeck, + recipient, + threadId); } else { inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), - recipient, - threadId); + messageRecord.getDateSent(), + author, + messageRecord.getBody(), + messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), + recipient, + threadId); } } @Override public void onMessageActionToolbarOpened() { - searchViewItem.collapseActionView(); + searchViewItem.collapseActionView(); } @Override @@ -2340,12 +2329,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateTitleTextView(Recipient recipient) { String userPublicKey = TextSecurePreferences.getLocalNumber(this); if (recipient == null) { - titleTextView.setText("Compose"); + titleTextView.setText(R.string.ConversationActivity_compose); } else if (recipient.getAddress().toString().toLowerCase().equals(userPublicKey)) { - titleTextView.setText(getResources().getString(R.string.note_to_self)); + titleTextView.setText(R.string.note_to_self); } else { - boolean hasName = (recipient.getName() != null && !recipient.getName().isEmpty()); - titleTextView.setText(hasName ? recipient.getName() : recipient.getAddress().toString()); + String displayName = recipient.getName(); // Uses the Contact API internally + boolean hasName = (displayName != null); + titleTextView.setText(hasName ? displayName : recipient.getAddress().toString()); } } @@ -2363,13 +2353,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity subtitleTextView.setVisibility(View.VISIBLE); if (recipient.isMuted()) { muteIndicatorImageView.setVisibility(View.VISIBLE); - subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault())); + subtitleTextView.setText(getString(R.string.ConversationActivity_muted_until_date,DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()))); } else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) { OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); if (openGroup != null) { Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(openGroup.getRoom(),openGroup.getServer()); if (userCount == null) { userCount = 0; } - subtitleTextView.setText(userCount + " members"); + subtitleTextView.setText(getString(R.string.ConversationActivity_member_count,userCount)); } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { subtitleTextView.setText(recipient.getAddress().toString()); } else { @@ -2391,7 +2381,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateMessageStatusProgressBar() { if (messageStatus != null) { - messageStatusProgressBar.setAlpha(1.0f); + messageStatusProgressBar.setAlpha(1.0f); switch (messageStatus) { case "calculatingPoW": setMessageStatusProgressAnimatedIfPossible(25); break; case "contactingNetwork": setMessageStatusProgressAnimatedIfPossible(50); break; @@ -2428,7 +2418,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity default: return -1; } } else { - return -1; + return -1; } } 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 7467cdcc78..c089feed6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -49,6 +49,8 @@ import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; + +import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.messaging.jobs.AttachmentDownloadJob; import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; @@ -889,7 +891,15 @@ public class ConversationItem extends LinearLayout @SuppressLint("SetTextI18n") private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) { if (groupThread && !messageRecord.isOutgoing()) { - String displayName = recipient.toShortString(); + String sessionID = recipient.getAddress().serialize(); + Contact contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID); + String displayName; + if (contact != null) { + Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR; + displayName = contact.displayName(context); + } else { + displayName = sessionID; + } this.groupSender.setText(displayName); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index ea534c35f8..9a87fa8730 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -17,11 +17,8 @@ package org.thoughtcrime.securesms.database; import android.content.Context; - import androidx.annotation.NonNull; - import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.attachments.DatabaseAttachmentProvider; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; @@ -34,6 +31,7 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; public class DatabaseFactory { @@ -55,16 +53,13 @@ public class DatabaseFactory { private final GroupReceiptDatabase groupReceiptDatabase; private final SearchDatabase searchDatabase; private final JobDatabase jobDatabase; - - // Loki private final LokiAPIDatabase lokiAPIDatabase; private final LokiMessageDatabase lokiMessageDatabase; private final LokiThreadDatabase lokiThreadDatabase; private final LokiUserDatabase lokiUserDatabase; private final LokiBackupFilesDatabase lokiBackupFilesDatabase; private final SessionJobDatabase sessionJobDatabase; - - // Refactor + private final SessionContactDatabase sessionContactDatabase; private final Storage storage; private final DatabaseAttachmentProvider attachmentProvider; @@ -157,6 +152,10 @@ public class DatabaseFactory { public static SessionJobDatabase getSessionJobDatabase(Context context) { return getInstance(context).sessionJobDatabase; } + + public static SessionContactDatabase getSessionContactDatabase(Context context) { + return getInstance(context).sessionContactDatabase; + } // endregion // region Refactor @@ -202,6 +201,7 @@ public class DatabaseFactory { this.storage = new Storage(context, databaseHelper); this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper); this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper); + this.sessionContactDatabase = new SessionContactDatabase(context, databaseHelper); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index d2b6ace217..287a6d365c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -248,6 +248,7 @@ public class RecipientDatabase extends Database { ContentValues contentValues = new ContentValues(1); contentValues.put(SYSTEM_DISPLAY_NAME, profileName); updateOrInsert(recipient.getAddress(), contentValues); + recipient.resolve().setName(profileName); recipient.resolve().setProfileName(profileName); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index d209955542..56a4b367f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue @@ -80,23 +80,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue)) } - override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? { - val address = Address.fromSerialized(recipientPublicKey) - val recipient = Recipient.from(context, address, false) - return recipient.profileKey - } - - override fun getDisplayNameForRecipient(recipientPublicKey: String): String? { - val database = DatabaseFactory.getLokiUserDatabase(context) - return database.getDisplayName(recipientPublicKey) - } - - override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { - val address = Address.fromSerialized(recipientPublicKey) - val recipient = Recipient.from(context, address, false) - DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey) - } - override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { @@ -512,16 +495,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return threadId } - override fun getDisplayName(publicKey: String): String? { - return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) + override fun getContactWithSessionID(sessionID: String): Contact? { + return DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID) } - override fun setDisplayName(publicKey: String, newName: String) { - DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) + override fun getAllContacts(): Set { + return DatabaseFactory.getSessionContactDatabase(context).getAllContacts() } - override fun getProfilePictureURL(publicKey: String): String? { - return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) + override fun setContact(contact: Contact) { + DatabaseFactory.getSessionContactDatabase(context).setContact(contact) } override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index d6c62f8f15..349f7a082b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration; @@ -57,9 +58,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV23 = 44; private static final int lokiV24 = 45; private static final int lokiV25 = 46; + private static final int lokiV26 = 47; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV25; + private static final int DATABASE_VERSION = lokiV26; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -124,11 +126,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); - db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand()); db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand()); db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType()); db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable()); + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -298,6 +300,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand()); } + if (oldVersion < lokiV26) { + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 03ea5168ae..7433d7a34f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -135,12 +135,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), }) // Set up remaining components if needed val application = ApplicationContext.getInstance(this) - val apiDB = DatabaseFactory.getLokiAPIDatabase(this) - val threadDB = DatabaseFactory.getLokiThreadDatabase(this) - val userDB = DatabaseFactory.getLokiUserDatabase(this) val userPublicKey = TextSecurePreferences.getLocalNumber(this) if (userPublicKey != null) { - MentionsManager.configureIfNeeded(userPublicKey, userDB) OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index c521c45c9e..ac342a707c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -72,7 +72,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) setContentView(R.layout.activity_settings) - val displayName = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey) + val displayName = TextSecurePreferences.getProfileName(this) ?: hexEncodedPublicKey glide = GlideApp.with(this) profilePictureView.glide = glide profilePictureView.publicKey = hexEncodedPublicKey diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 15e76ac0ec..91f04adef8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -278,14 +278,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } } - override fun getLastMessageServerID(group: Long, server: String): Long? { - val database = databaseHelper.readableDatabase - val index = "$server.$group" - return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor -> - cursor.getInt(lastMessageServerID) - }?.toLong() - } - override fun getLastMessageServerID(room: String, server: String): Long? { val database = databaseHelper.writableDatabase val index = "$server.$room" @@ -294,13 +286,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toLong() } - override fun setLastMessageServerID(group: Long, server: String, newValue: Long) { - val database = databaseHelper.writableDatabase - val index = "$server.$group" - val row = wrap(mapOf( lastMessageServerIDTableIndex to index, lastMessageServerID to newValue.toString() )) - database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index)) - } - override fun setLastMessageServerID(room: String, server: String, newValue: Long) { val database = databaseHelper.writableDatabase val index = "$server.$room" @@ -308,26 +293,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index)) } - fun removeLastMessageServerID(group: Long, server: String) { - val database = databaseHelper.writableDatabase - val index = "$server.$group" - database.delete(lastMessageServerIDTable,"$lastMessageServerIDTableIndex = ?", wrap(index)) - } - fun removeLastMessageServerID(room: String, server:String) { val database = databaseHelper.writableDatabase val index = "$server.$room" database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) } - override fun getLastDeletionServerID(group: Long, server: String): Long? { - val database = databaseHelper.readableDatabase - val index = "$server.$group" - return database.get(lastDeletionServerIDTable, "$lastDeletionServerIDTableIndex = ?", wrap(index)) { cursor -> - cursor.getInt(lastDeletionServerID) - }?.toLong() - } - override fun getLastDeletionServerID(room: String, server: String): Long? { val database = databaseHelper.readableDatabase val index = "$server.$room" @@ -336,13 +307,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toLong() } - override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) { - val database = databaseHelper.writableDatabase - val index = "$server.$group" - val row = wrap(mapOf( lastDeletionServerIDTableIndex to index, lastDeletionServerID to newValue.toString() )) - database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index)) - } - override fun setLastDeletionServerID(room: String, server: String, newValue: Long) { val database = databaseHelper.writableDatabase val index = "$server.$room" @@ -392,32 +356,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index)) } - override fun getSessionRequestSentTimestamp(publicKey: String): Long? { - val database = databaseHelper.readableDatabase - return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor -> - cursor.getLong(LokiAPIDatabase.timestamp) - }?.toLong() - } - - override fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) { - val database = databaseHelper.writableDatabase - val row = wrap(mapOf( LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to newValue.toString() )) - database.insertOrUpdate(sessionRequestSentTimestampTable, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) - } - - override fun getSessionRequestProcessedTimestamp(publicKey: String): Long? { - val database = databaseHelper.readableDatabase - return database.get(sessionRequestProcessedTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor -> - cursor.getInt(LokiAPIDatabase.timestamp) - }?.toLong() - } - - override fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) { - val database = databaseHelper.writableDatabase - val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to newValue.toString())) - database.insertOrUpdate(sessionRequestProcessedTimestampTable, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) - } - override fun getOpenGroupPublicKey(server: String): String? { val database = databaseHelper.readableDatabase return database.get(openGroupPublicKeyTable, "${LokiAPIDatabase.server} = ?", wrap(server)) { cursor -> @@ -431,27 +369,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server)) } - override fun getOpenGroupProfilePictureURL(group: Long, server: String): String? { - val database = databaseHelper.readableDatabase - val index = "$server.$group" - return database.get(openGroupProfilePictureTable, "$publicChatID = ?", wrap(index)) { cursor -> - cursor.getString(openGroupProfilePicture) - }?.toString() - } - - override fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) { - val database = databaseHelper.writableDatabase - val index = "$server.$group" - val row = wrap(mapOf(publicChatID to index, openGroupProfilePicture to newValue)) - database.insertOrUpdate(openGroupProfilePictureTable, row, "$publicChatID = ?", wrap(index)) - } - - fun clearOpenGroupProfilePictureURL(group: Long, server: String): Boolean { - val database = databaseHelper.writableDatabase - val index = "$server.$group" - return database.delete(openGroupProfilePictureTable, "$publicChatID = ?", arrayOf(index)) > 0 - } - override fun getLastSnodePoolRefreshDate(): Date? { val time = TextSecurePreferences.getLastSnodePoolRefreshDate(context) if (time <= 0) { return null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt index bc2d71f7c6..83e64f884c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt @@ -37,11 +37,6 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab } - override fun getQuoteServerID(quoteID: Long, quoteePublicKey: String): Long? { - val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, quoteePublicKey) - return if (message != null) getServerID(message.getId(), !message.isMms) else null - } - fun getServerID(messageID: Long): Long? { val database = databaseHelper.readableDatabase return database.get(messageIDTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt index 1a7efd48ff..fba0d64b36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt @@ -1,19 +1,12 @@ package org.thoughtcrime.securesms.loki.database -import android.content.ContentValues import android.content.Context -import android.database.sqlite.SQLiteDatabase -import org.session.libsignal.utilities.Log -import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.get -import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.database.LokiUserDatabaseProtocol -class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiUserDatabaseProtocol { +class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { companion object { // Shared @@ -28,7 +21,7 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database @JvmStatic val createServerDisplayNameTableCommand = "CREATE TABLE $serverDisplayNameTable ($publicKey TEXT, $serverID TEXT, $displayName TEXT, PRIMARY KEY ($publicKey, $serverID));" } - override fun getDisplayName(publicKey: String): String? { + fun getDisplayName(publicKey: String): String? { if (publicKey == TextSecurePreferences.getLocalNumber(context)) { return TextSecurePreferences.getProfileName(context) } else { @@ -44,21 +37,4 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database } } } - - fun setDisplayName(publicKey: String, displayName: String) { - val database = databaseHelper.writableDatabase - val row = ContentValues(2) - row.put(Companion.publicKey, publicKey) - row.put(Companion.displayName, displayName) - database.insertOrUpdate(displayNameTable, row, "${Companion.publicKey} = ?", arrayOf( publicKey )) - Recipient.from(context, Address.fromSerialized(publicKey), false).notifyListeners() - } - - override fun getProfilePictureURL(publicKey: String): String? { - return if (publicKey == TextSecurePreferences.getLocalNumber(context)) { - TextSecurePreferences.getProfilePictureURL(context) - } else { - Recipient.from(context, Address.fromSerialized(publicKey), false).resolve().profileAvatar - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt new file mode 100644 index 0000000000..7d5e93b502 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -0,0 +1,81 @@ +package org.thoughtcrime.securesms.loki.database + +import android.content.ContentValues +import android.content.Context +import net.sqlcipher.Cursor +import org.session.libsession.messaging.contacts.Contact +import org.session.libsignal.utilities.Base64 +import org.thoughtcrime.securesms.database.Database +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.loki.utilities.* + +class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { + + companion object { + private const val sessionContactTable = "session_contact_database" + const val sessionID = "session_id" + const val name = "name" + const val nickname = "nickname" + const val profilePictureURL = "profile_picture_url" + const val profilePictureFileName = "profile_picture_file_name" + const val profilePictureEncryptionKey = "profile_picture_encryption_key" + const val threadID = "thread_id" + const val isTrusted = "is_trusted" + @JvmStatic val createSessionContactTableCommand = + "CREATE TABLE $sessionContactTable " + + "($sessionID STRING PRIMARY KEY, " + + "$name TEXT DEFAULT NULL, " + + "$nickname TEXT DEFAULT NULL, " + + "$profilePictureURL TEXT DEFAULT NULL, " + + "$profilePictureFileName TEXT DEFAULT NULL, " + + "$profilePictureEncryptionKey BLOB DEFAULT NULL, " + + "$threadID INTEGER DEFAULT -1, " + + "$isTrusted INTEGER DEFAULT 0);" + } + + fun getContactWithSessionID(sessionID: String): Contact? { + val database = databaseHelper.readableDatabase + return database.get(sessionContactTable, "${SessionContactDatabase.sessionID} = ?", arrayOf( sessionID )) { cursor -> + contactFromCursor(cursor) + } + } + + fun getAllContacts(): Set { + val database = databaseHelper.readableDatabase + return database.getAll(sessionContactTable, null, null) { cursor -> + contactFromCursor(cursor) + }.toSet() + } + + fun setContact(contact: Contact) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(8) + contentValues.put(sessionID, contact.sessionID) + contentValues.put(name, contact.name) + contentValues.put(nickname, contact.nickname) + contentValues.put(profilePictureURL, contact.profilePictureURL) + contentValues.put(profilePictureFileName, contact.profilePictureFileName) + contact.profilePictureEncryptionKey?.let { + contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(it)) + } + contentValues.put(threadID, threadID) + contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) + database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf( contact.sessionID )) + notifyConversationListListeners() + } + + private fun contactFromCursor(cursor: Cursor): Contact { + val sessionID = cursor.getString(sessionID) + val contact = Contact(sessionID) + contact.name = cursor.getStringOrNull(name) + contact.nickname = cursor.getStringOrNull(nickname) + contact.profilePictureURL = cursor.getStringOrNull(profilePictureURL) + contact.profilePictureFileName = cursor.getStringOrNull(profilePictureFileName) + cursor.getStringOrNull(profilePictureEncryptionKey)?.let { + contact.profilePictureEncryptionKey = Base64.decode(it) + } + contact.threadID = cursor.getLong(threadID) + contact.isTrusted = cursor.getInt(isTrusted) != 0 + return contact + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt deleted file mode 100644 index dff4e5fa30..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.thoughtcrime.securesms.loki.dialogs - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import kotlinx.android.synthetic.main.fragment_key_pair_migration_bottom_sheet.* -import network.loki.messenger.R -import org.thoughtcrime.securesms.ApplicationContext - -class KeyPairMigrationBottomSheet : BottomSheetDialogFragment() { - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_key_pair_migration_bottom_sheet, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - upgradeNowButton.setOnClickListener { upgradeNow() } - upgradeLaterButton.setOnClickListener { upgradeLater() } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val dialog = super.onCreateDialog(savedInstanceState) - dialog.setOnShowListener { - val d = dialog as BottomSheetDialog - val bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet)!! - BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED - BottomSheetBehavior.from(bottomSheet).isHideable = false - } - isCancelable = false - return dialog - } - - private fun upgradeNow() { - val applicationContext = requireContext().applicationContext as ApplicationContext - applicationContext.clearAllData(true) - } - - private fun upgradeLater() { - val dialog = AlertDialog.Builder(requireContext()) - dialog.setMessage("You won't be able to send or receive messages until you upgrade.") - dialog.setPositiveButton(R.string.ok) { _, _ -> - dismiss() - } - dialog.create().show() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationSuccessBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationSuccessBottomSheet.kt deleted file mode 100644 index 21ab2c5f34..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationSuccessBottomSheet.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.thoughtcrime.securesms.loki.dialogs - -import android.app.AlertDialog -import android.app.Dialog -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.Toast -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import kotlinx.android.synthetic.main.fragment_key_pair_migration_success_bottom_sheet.* -import network.loki.messenger.R -import org.session.libsession.utilities.TextSecurePreferences - -class KeyPairMigrationSuccessBottomSheet : BottomSheetDialogFragment() { - - private val sessionID by lazy { - TextSecurePreferences.getLocalNumber(requireContext()) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_key_pair_migration_success_bottom_sheet, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - sessionIDTextView.text = sessionID - copyButton.setOnClickListener { copySessionID() } - okButton.setOnClickListener { dismiss() } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val dialog = super.onCreateDialog(savedInstanceState) - // Expand the bottom sheet by default - dialog.setOnShowListener { - val d = dialog as BottomSheetDialog - val bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet) - BottomSheetBehavior.from(bottomSheet!!).setState(BottomSheetBehavior.STATE_EXPANDED); - } - return dialog - } - - private fun copySessionID() { - val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Session ID", sessionID) - clipboard.setPrimaryClip(clip) - Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt index 3d884f7440..c020fc3aee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.loki.dialogs +import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -8,14 +9,19 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager import android.widget.Toast import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* -import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.mms.GlideApp -public class UserDetailsBottomSheet : BottomSheetDialogFragment() { +class UserDetailsBottomSheet : BottomSheetDialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_user_details_bottom_sheet, container, false) @@ -24,11 +30,38 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val publicKey = arguments?.getString("publicKey") ?: return dismiss() + val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) profilePictureView.publicKey = publicKey profilePictureView.glide = GlideApp.with(this) profilePictureView.isLarge = true profilePictureView.update() - nameTextView.text = DatabaseFactory.getLokiUserDatabase(requireContext()).getDisplayName(publicKey) ?: "Anonymous" + nameTextViewContainer.visibility = View.VISIBLE + nameTextViewContainer.setOnClickListener { + nameTextViewContainer.visibility = View.INVISIBLE + nameEditTextContainer.visibility = View.VISIBLE + nicknameEditText.text = null + nicknameEditText.requestFocus() + showSoftKeyboard() + } + cancelNicknameEditingButton.setOnClickListener { + nicknameEditText.clearFocus() + hideSoftKeyboard() + nameTextViewContainer.visibility = View.VISIBLE + nameEditTextContainer.visibility = View.INVISIBLE + } + saveNicknameButton.setOnClickListener { + saveNickName(recipient) + } + nicknameEditText.setOnEditorActionListener { _, actionId, _ -> + when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + saveNickName(recipient) + return@setOnEditorActionListener true + } + else -> return@setOnEditorActionListener false + } + } + nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally publicKeyTextView.text = publicKey copyButton.setOnClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -37,4 +70,32 @@ public class UserDetailsBottomSheet : BottomSheetDialogFragment() { Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() } } + + fun saveNickName(recipient: Recipient) { + nicknameEditText.clearFocus() + hideSoftKeyboard() + nameTextViewContainer.visibility = View.VISIBLE + nameEditTextContainer.visibility = View.INVISIBLE + var newNickName: String? = null + if (nicknameEditText.text.isNotEmpty()) { + newNickName = nicknameEditText.text.toString() + } + val publicKey = recipient.address.serialize() + val contactDB = DatabaseFactory.getSessionContactDatabase(context) + val contact = contactDB.getContactWithSessionID(publicKey) ?: Contact(publicKey) + contact.nickname = newNickName + contactDB.setContact(contact) + nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally + } + + @SuppressLint("ServiceCast") + fun showSoftKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.showSoftInput(nicknameEditText, 0) + } + + fun hideSoftKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(nicknameEditText.windowToken, 0) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt index b270cdecf6..1f8bdca7ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt @@ -1,15 +1,8 @@ package org.thoughtcrime.securesms.loki.protocol -import android.content.Context -import org.thoughtcrime.securesms.ApplicationContext import org.session.libsession.utilities.Address -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.messages.SignalServiceContent import org.session.libsignal.messages.SignalServiceDataMessage -import java.security.MessageDigest object SessionMetaProtocol { @@ -39,19 +32,6 @@ object SessionMetaProtocol { return shouldIgnoreMessage } - @JvmStatic - fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) { - val displayName = content.senderDisplayName.orNull() ?: return - if (displayName.isBlank()) { return } - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val sender = content.sender.toLowerCase() - if (userPublicKey == sender) { - // Update the user's local name if the message came from their master device - TextSecurePreferences.setProfileName(context, displayName) - } - DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, displayName) - } - @JvmStatic fun canUserReplyToNotification(recipient: Recipient): Boolean { // TODO return !recipient.address.isRSSFeed diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt index 140586930f..23834fb9af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DatabaseUtilities.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.loki.utilities import android.content.ContentValues +import androidx.core.database.getStringOrNull import net.sqlcipher.Cursor import net.sqlcipher.database.SQLiteDatabase import org.session.libsignal.utilities.Base64 @@ -56,4 +57,8 @@ fun Cursor.getLong(columnName: String): Long { fun Cursor.getBase64EncodedData(columnName: String): ByteArray { return Base64.decode(getString(columnName)) +} + +fun Cursor.getStringOrNull(columnName: String): String? { + return getStringOrNull(getColumnIndexOrThrow(columnName)) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt index faef8f0cc3..000b61db89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt @@ -15,7 +15,7 @@ object MentionManagerUtilities { val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.address.toGroupString(), false).map { it.address.serialize() } result.addAll(members) } else { - if (MentionsManager.shared.userPublicKeyCache[threadID] != null) { return } + if (MentionsManager.userPublicKeyCache[threadID] != null) { return } val messageDatabase = DatabaseFactory.getMmsSmsDatabase(context) val reader = messageDatabase.readerFor(messageDatabase.getConversation(threadID)) var record: MessageRecord? = reader.next @@ -30,6 +30,6 @@ object MentionManagerUtilities { reader.close() result.add(TextSecurePreferences.getLocalNumber(context)!!) } - MentionsManager.shared.userPublicKeyCache[threadID] = result + MentionsManager.userPublicKeyCache[threadID] = result } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt index 300d5466a0..45fda301d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt @@ -9,6 +9,7 @@ import android.text.style.StyleSpan import android.util.Range import network.loki.messenger.R import nl.komponents.kovenant.combine.Tuple2 +import org.session.libsession.messaging.contacts.Contact import org.thoughtcrime.securesms.database.DatabaseFactory import org.session.libsession.utilities.TextSecurePreferences import java.util.regex.Pattern @@ -23,6 +24,8 @@ object MentionUtilities { @JvmStatic fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString { var text = text + val threadDB = DatabaseFactory.getThreadDatabase(context) + val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false val pattern = Pattern.compile("@[0-9a-fA-F]*") var matcher = pattern.matcher(text) val mentions = mutableListOf, String>>() @@ -31,10 +34,12 @@ object MentionUtilities { if (matcher.find(startIndex)) { while (true) { val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @ - val userDisplayName: String? = if (publicKey.toLowerCase() == userPublicKey.toLowerCase()) { + val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true)) { TextSecurePreferences.getProfileName(context) } else { - DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) + val contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(publicKey) + val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR + contact?.displayName(context) } if (userDisplayName != null) { text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt deleted file mode 100644 index ad3d35f809..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:JvmName("NotificationUtilities") -package org.thoughtcrime.securesms.loki.utilities - -import android.content.Context -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.session.libsession.utilities.recipients.Recipient - -fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String { - val publicKey = recipient.address.toString() - val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - // FIXME: Add short ID here? - return displayName ?: publicKey -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt index e1a308b645..efbaa24dbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt @@ -2,13 +2,15 @@ package org.thoughtcrime.securesms.loki.views import android.content.Context import android.graphics.Typeface -import android.text.TextUtils import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded @@ -56,7 +58,7 @@ class ConversationView : LinearLayout { } profilePictureView.glide = glide profilePictureView.update(thread.recipient, thread.threadId) - val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else if (!thread.recipient.name.isNullOrEmpty()) thread.recipient.name else thread.recipient.address.toString() + val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() btnGroupNameDisplay.text = senderDisplayName timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date) muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE @@ -85,9 +87,12 @@ class ConversationView : LinearLayout { profilePictureView.recycle() } - private fun getUserDisplayName(publicKey: String?): String? { - if (TextUtils.isEmpty(publicKey)) return null - return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!) + private fun getUserDisplayName(recipient: Recipient): String? { + if (recipient.isLocalNumber) { + return context.getString(R.string.note_to_self) + } else { + return recipient.name // Internally uses the Contact API + } } // endregion } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index 2226e9ef8b..e2de25ca90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -11,6 +11,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy import kotlinx.android.synthetic.main.view_profile_picture.view.* import network.loki.messenger.R import org.session.libsession.avatars.ProfileContactPhoto +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient @@ -57,19 +58,15 @@ class ProfilePictureView : RelativeLayout { // region Updating fun update(recipient: Recipient, threadID: Long) { - fun getUserDisplayName(publicKey: String?): String? { - if (publicKey == null || publicKey.isBlank()) { - return null - } else { - val result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - return result ?: publicKey - } + fun getUserDisplayName(publicKey: String): String { + val contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(publicKey) + return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } fun isOpenGroupWithProfilePicture(recipient: Recipient): Boolean { return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null } if (recipient.isGroupRecipient && !isOpenGroupWithProfilePicture(recipient)) { - val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() + val users = MentionsManager.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() users.remove(TextSecurePreferences.getLocalNumber(context)) val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability if (users.count() == 1) { @@ -86,7 +83,8 @@ class ProfilePictureView : RelativeLayout { recipient.name == "Session Updates" || recipient.name == "Session Public Chat" } else { - publicKey = recipient.address.toString() + val publicKey = recipient.address.toString() + this.publicKey = publicKey displayName = getUserDisplayName(publicKey) additionalPublicKey = null isRSSFeed = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt index 4e35ba1869..7dae953974 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt @@ -8,6 +8,7 @@ import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView import kotlinx.android.synthetic.main.view_user.view.* import network.loki.messenger.R +import org.session.libsession.messaging.contacts.Contact import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities import org.thoughtcrime.securesms.mms.GlideRequests @@ -48,13 +49,9 @@ class UserView : LinearLayout { // region Updating fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) { - fun getUserDisplayName(publicKey: String?): String? { - if (publicKey == null || publicKey.isBlank()) { - return null - } else { - val result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - return result ?: publicKey - } + fun getUserDisplayName(publicKey: String): String { + val contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(publicKey) + return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(user) MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 9527282d39..f094cea12c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -8,14 +8,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import android.text.SpannableStringBuilder; - +import org.session.libsession.messaging.contacts.Contact; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.loki.activities.HomeActivity; -import org.thoughtcrime.securesms.loki.utilities.NotificationUtilities; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; - import java.util.LinkedList; import java.util.List; @@ -49,8 +49,15 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) { String displayName = recipient.toShortString(); - if (threadRecipient.isGroupRecipient()) { - displayName = NotificationUtilities.getOpenGroupDisplayName(recipient, threadRecipient, context); + if (threadRecipient.isOpenGroupRecipient()) { + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = recipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact != null) { + displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + } else { + displayName = sessionID; + } } if (privacy.isDisplayContact()) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); @@ -71,8 +78,15 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) { String displayName = sender.toShortString(); - if (threadRecipient.isGroupRecipient()) { - displayName = NotificationUtilities.getOpenGroupDisplayName(sender, threadRecipient, context); + if (threadRecipient.isOpenGroupRecipient()) { + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = sender.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact != null) { + displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + } else { + displayName = sessionID; + } } if (privacy.isDisplayMessage()) { SpannableStringBuilder builder = new SpannableStringBuilder(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 0ed628034f..008779301b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -15,22 +15,21 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.text.SpannableStringBuilder; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Action; import androidx.core.app.RemoteInput; - import com.bumptech.glide.load.engine.DiskCacheStrategy; - import org.session.libsession.avatars.ContactColors; import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.avatars.GeneratedContactPhoto; +import org.session.libsession.messaging.contacts.Contact; import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator; -import org.thoughtcrime.securesms.loki.utilities.NotificationUtilities; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.Slide; @@ -40,11 +39,9 @@ import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.util.BitmapUtil; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; - import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; - import network.loki.messenger.R; public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder { @@ -121,8 +118,16 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipients.isGroupRecipient()) { - String displayName = NotificationUtilities.getOpenGroupDisplayName(individualRecipient, threadRecipients, context); + if (privacy.isDisplayContact() && threadRecipients.isOpenGroupRecipient()) { + String displayName; + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = individualRecipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact != null) { + displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + } else { + displayName = sessionID; + } if (displayName != null) { stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -209,8 +214,16 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { - String displayName = NotificationUtilities.getOpenGroupDisplayName(individualRecipient, threadRecipient, context); + if (privacy.isDisplayContact() && threadRecipient.isOpenGroupRecipient()) { + String displayName; + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = individualRecipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact != null) { + displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + } else { + displayName = sessionID; + } if (displayName != null) { stringBuilder.append(Util.getBoldedString(displayName + ": ")); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 7bb7970ea6..e7fdbc509f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -1,22 +1,70 @@ package org.thoughtcrime.securesms.sskenvironment import android.content.Context +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob -class ProfileManager: SSKEnvironment.ProfileManagerProtocol { - override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { - DatabaseFactory.getLokiUserDatabase(context).setDisplayName(recipient.address.serialize(), displayName) +class ProfileManager : SSKEnvironment.ProfileManagerProtocol { + + override fun setNickname(context: Context, recipient: Recipient, nickname: String?) { + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadId(recipient.address) + if (contact.nickname != nickname) { + contact.nickname = nickname + contactDatabase.setContact(contact) + } + } + + override fun setName(context: Context, recipient: Recipient, name: String) { + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadId(recipient.address) + if (contact.name != name) { + contact.name = name + contactDatabase.setContact(contact) + } + // Old API + val database = DatabaseFactory.getRecipientDatabase(context) + database.setProfileName(recipient, name) + recipient.notifyListeners() } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, profilePictureURL)) + val job = RetrieveProfileAvatarJob(recipient, profilePictureURL) + ApplicationContext.getInstance(context).jobManager.add(job) + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadId(recipient.address) + if (contact.profilePictureURL != profilePictureURL) { + contact.profilePictureURL = profilePictureURL + contactDatabase.setContact(contact) + } } override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) { + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + contact.threadID = DatabaseFactory.getStorage(context).getThreadId(recipient.address) + if (!contact.profilePictureEncryptionKey.contentEquals(profileKey)) { + contact.profilePictureEncryptionKey = profileKey + contactDatabase.setContact(contact) + } + // Old API val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileKey(recipient, profileKey) } diff --git a/app/src/main/res/layout-sw400dp/activity_landing.xml b/app/src/main/res/layout-sw400dp/activity_landing.xml index 52906283a7..dc36e5fa57 100644 --- a/app/src/main/res/layout-sw400dp/activity_landing.xml +++ b/app/src/main/res/layout-sw400dp/activity_landing.xml @@ -59,6 +59,6 @@ android:background="@color/transparent" android:textAllCaps="false" android:textSize="@dimen/medium_font_size" - android:text="Link a Device" /> + android:text="@string/activity_link_device_link_device" /> \ No newline at end of file diff --git a/app/src/main/res/layout-sw400dp/activity_pn_mode.xml b/app/src/main/res/layout-sw400dp/activity_pn_mode.xml index fb1fc1c6ca..71cfc81180 100644 --- a/app/src/main/res/layout-sw400dp/activity_pn_mode.xml +++ b/app/src/main/res/layout-sw400dp/activity_pn_mode.xml @@ -19,7 +19,7 @@ android:textSize="@dimen/very_large_font_size" android:textStyle="bold" android:textColor="@color/text" - android:text="Message Notifications" /> + android:text="@string/activity_pn_mode_message_notifications" /> + android:text="@string/activity_pn_mode_explanation" /> + android:text="@string/activity_pn_mode_fast_mode" /> + android:text="@string/activity_pn_mode_fast_mode_explanation" /> + android:text="@string/activity_pn_mode_slow_mode" /> + android:text="@string/activity_pn_mode_slow_mode_explanation" /> diff --git a/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml b/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml index 6f3535ede0..83bd3e1244 100644 --- a/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml +++ b/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml @@ -1,6 +1,7 @@ @@ -69,6 +70,7 @@ android:textColor="?android:textColorTertiary" android:textColorLink="?colorAccent" android:textSize="@dimen/very_small_font_size" - android:text="By using this service, you agree to our Terms of Service and Privacy Policy" /> + android:text="By using this service, you agree to our Terms of Service and Privacy Policy" + tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout-sw400dp/activity_register.xml b/app/src/main/res/layout-sw400dp/activity_register.xml index 8cc432c160..76e4e3e749 100644 --- a/app/src/main/res/layout-sw400dp/activity_register.xml +++ b/app/src/main/res/layout-sw400dp/activity_register.xml @@ -1,6 +1,7 @@ @@ -38,7 +39,7 @@ android:layout_marginLeft="@dimen/very_large_spacing" android:layout_marginTop="@dimen/medium_spacing" android:layout_marginRight="@dimen/very_large_spacing" - android:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> + tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> + android:text="By using this service, you agree to our Terms of Service and Privacy Policy" + tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout-sw400dp/activity_seed.xml b/app/src/main/res/layout-sw400dp/activity_seed.xml index 74f44a79d4..3592134df9 100644 --- a/app/src/main/res/layout-sw400dp/activity_seed.xml +++ b/app/src/main/res/layout-sw400dp/activity_seed.xml @@ -1,8 +1,8 @@ - @@ -47,7 +47,7 @@ android:gravity="center" android:textSize="16sp" android:textAlignment="center" - android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" /> + tools:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" /> + android:text="@string/fragment_recovery_phrase_title" /> + android:text="@string/activity_restore_explanation" /> + android:text="@string/view_seed_reminder_title" /> + android:text="@string/view_seed_reminder_subtitle_1" /> @@ -64,7 +64,7 @@ android:layout_height="28dp" android:layout_marginLeft="4dp" android:textStyle="normal" - android:text="Continue" /> + android:text="@string/continue_2" /> diff --git a/app/src/main/res/layout/activity_backup_restore.xml b/app/src/main/res/layout/activity_backup_restore.xml index 365ff39cd1..af86967c77 100644 --- a/app/src/main/res/layout/activity_backup_restore.xml +++ b/app/src/main/res/layout/activity_backup_restore.xml @@ -99,7 +99,8 @@ android:text="By using this service, you agree to our Terms of Service and Privacy Policy" android:textColor="@color/text" android:textColorLink="@color/text" - android:textSize="@dimen/very_small_font_size" /> + android:textSize="@dimen/very_small_font_size" + tools:ignore="HardcodedText" /> diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 49b5d36053..a732ad3a59 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -42,7 +42,7 @@ android:layout_centerVertical="true" android:layout_marginLeft="64dp" android:fontFamily="sans-serif-medium" - android:text="Session" + android:text="@string/app_name" android:textColor="@color/text" android:textSize="@dimen/very_large_font_size" /> diff --git a/app/src/main/res/layout/activity_landing.xml b/app/src/main/res/layout/activity_landing.xml index b2b4c3cde6..2858913c18 100644 --- a/app/src/main/res/layout/activity_landing.xml +++ b/app/src/main/res/layout/activity_landing.xml @@ -59,6 +59,6 @@ android:background="@color/transparent" android:textAllCaps="false" android:textSize="@dimen/medium_font_size" - android:text="Link a Device" /> + android:text="@string/activity_link_device_link_device" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_open_group_guidelines.xml b/app/src/main/res/layout/activity_open_group_guidelines.xml index 34d03a8f89..4105e48187 100644 --- a/app/src/main/res/layout/activity_open_group_guidelines.xml +++ b/app/src/main/res/layout/activity_open_group_guidelines.xml @@ -18,7 +18,7 @@ android:textSize="@dimen/large_font_size" android:textStyle="bold" android:textColor="@color/text" - android:text="Community Guidelines" /> + android:text="@string/ConversationActivity_open_group_guidelines" /> + android:text="@string/activity_pn_mode_message_notifications" /> + android:text="@string/activity_pn_mode_explanation" /> + android:text="@string/activity_pn_mode_fast_mode" /> + android:text="@string/activity_pn_mode_fast_mode_explanation" /> + android:text="@string/activity_pn_mode_slow_mode" /> + android:text="@string/activity_pn_mode_slow_mode_explanation" /> diff --git a/app/src/main/res/layout/activity_recovery_phrase_restore.xml b/app/src/main/res/layout/activity_recovery_phrase_restore.xml index 5fede38026..47cf545565 100644 --- a/app/src/main/res/layout/activity_recovery_phrase_restore.xml +++ b/app/src/main/res/layout/activity_recovery_phrase_restore.xml @@ -1,6 +1,7 @@ @@ -69,6 +70,7 @@ android:textColorLink="@color/text" android:textSize="@dimen/very_small_font_size" android:textColor="@color/text" - android:text="By using this service, you agree to our Terms of Service and Privacy Policy" /> + android:text="By using this service, you agree to our Terms of Service and Privacy Policy" + tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml index 57a64e2fb3..0f0d560b86 100644 --- a/app/src/main/res/layout/activity_register.xml +++ b/app/src/main/res/layout/activity_register.xml @@ -1,6 +1,7 @@ @@ -35,11 +36,11 @@ android:id="@+id/publicKeyTextView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textSize="18dp" + android:textSize="18sp" android:layout_marginLeft="@dimen/very_large_spacing" android:layout_marginTop="10dp" android:layout_marginRight="@dimen/very_large_spacing" - android:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> + tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> + android:text="By using this service, you agree to our Terms of Service and Privacy Policy" + tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_seed.xml b/app/src/main/res/layout/activity_seed.xml index 8aacbf3df3..60a61b7860 100644 --- a/app/src/main/res/layout/activity_seed.xml +++ b/app/src/main/res/layout/activity_seed.xml @@ -1,8 +1,8 @@ - @@ -47,7 +47,7 @@ android:gravity="center" android:textSize="14sp" android:textAlignment="center" - android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" /> + tools:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" /> + tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> + android:text="@string/activity_settings_help_translate_session" /> diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index ba9f7f27e1..e920d5f540 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -55,9 +55,10 @@ android:id="@+id/titleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:background="@null" android:maxLines="1" android:ellipsize="end" - android:text="Conversation" + tools:text="Conversation" android:textColor="@color/text" android:textSize="@dimen/large_font_size" android:fontFamily="sans-serif-medium" /> @@ -83,7 +84,7 @@ android:layout_height="wrap_content" android:maxLines="1" android:ellipsize="end" - android:text="26 members" + tools:text="26 members" android:textColor="@color/text" android:textSize="@dimen/small_font_size" /> @@ -217,7 +218,7 @@ android:layout_height="wrap_content" android:background="?android:attr/windowBackground" android:paddingStart="5dip" - android:text="160/160 (1)" + tools:text="160/160 (1)" android:visibility="gone" /> - diff --git a/app/src/main/res/layout/fragment_enter_public_key.xml b/app/src/main/res/layout/fragment_enter_public_key.xml index ffd53dd34e..1caef0c4bc 100644 --- a/app/src/main/res/layout/fragment_enter_public_key.xml +++ b/app/src/main/res/layout/fragment_enter_public_key.xml @@ -1,6 +1,6 @@ - + tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> - - - - - - - - - - -