diff --git a/app/build.gradle b/app/build.gradle index 6f84c6ca8..1596b7f67 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ buildscript { - ext.kotlin_version = "1.4.0" ext.kovenant_version = "3.3.0" repositories { @@ -124,8 +123,8 @@ dependencies { implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion" implementation "com.github.lelloman:android-identicons:v11" 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 b202f418c..20c9fe83f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -249,7 +249,14 @@ public class RecipientDatabase extends Database { recipient.resolve().setProfileAvatar(profileAvatar); } - public void setProfileSharing(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean enabled) { + public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(SYSTEM_DISPLAY_NAME, profileName); + updateOrInsert(recipient.getAddress(), contentValues); + recipient.resolve().setProfileName(profileName); + } + + public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) { ContentValues contentValues = new ContentValues(1); contentValues.put(PROFILE_SHARING, enabled ? 1 : 0); updateOrInsert(recipient.getAddress(), contentValues); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 1bd87e3b6..25a303112 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -506,6 +506,10 @@ public class ThreadDatabase extends Database { notifyConversationListeners(threadId); } + public void notifyUpdatedFromConfig() { + notifyConversationListListeners(); + } + public boolean update(long threadId, boolean unarchive) { MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context); long count = mmsSmsDatabase.getConversationCount(threadId); 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 583ea4527..284889570 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 @@ -22,9 +22,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_home.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R +import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity @@ -46,7 +48,10 @@ import org.thoughtcrime.securesms.loki.dialogs.* import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2 import java.io.IOException -class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate { +class HomeActivity : PassphraseRequiredActionBarActivity, + ConversationClickListener, + SeedReminderViewDelegate, + NewConversationButtonSetViewDelegate { private lateinit var glide: GlideRequests private var broadcastReceiver: BroadcastReceiver? = null @@ -71,9 +76,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe glide = GlideApp.with(this) // Set up toolbar buttons profileButton.glide = glide - profileButton.publicKey = publicKey - profileButton.displayName = TextSecurePreferences.getProfileName(this) - profileButton.update() + updateProfileButton() profileButton.setOnClickListener { openSettings() } pathStatusViewContainer.disableClipping() pathStatusViewContainer.setOnClickListener { showPath() } @@ -149,6 +152,12 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe } this.broadcastReceiver = broadcastReceiver LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged")) + lifecycleScope.launchWhenResumed { + // update things based on TextSecurePrefs (profile info etc) + TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect { + updateProfileButton() + } + } } override fun onResume() { @@ -198,6 +207,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val threadCount = (recyclerView.adapter as HomeAdapter).itemCount emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE } + + private fun updateProfileButton() { + profileButton.publicKey = publicKey + profileButton.displayName = TextSecurePreferences.getProfileName(this) + profileButton.recycle() + profileButton.update() + } // endregion // region Interaction @@ -356,7 +372,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe private fun openSettings() { val intent = Intent(this, SettingsActivity::class.java) - show(intent) + show(intent, isForResult = true) } private fun showPath() { 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 fa21d1f16..a9296e918 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 @@ -68,6 +68,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { return TextSecurePreferences.getLocalNumber(this)!! } + companion object { + const val updatedProfileResultCode = 1234 + } + // region Lifecycle override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index 8b52e0166..f7f8be88e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -12,12 +12,18 @@ import org.session.libsignal.service.api.push.SignalServiceAddress import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.utilities.ContactUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities +import org.thoughtcrime.securesms.sskenvironment.ProfileManager import java.util.* object MultiDeviceProtocol { @@ -76,7 +82,7 @@ object MultiDeviceProtocol { // TODO: remove this after we migrate to new message receiving pipeline @JvmStatic fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) { - if (TextSecurePreferences.getConfigurationMessageSynced(context)) return +// if (TextSecurePreferences.getConfigurationMessageSynced(context)) return val configurationMessage = ConfigurationMessage.fromProto(content) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return if (senderPublicKey != userPublicKey) return @@ -103,6 +109,37 @@ object MultiDeviceProtocol { if (allOpenGroups.contains(openGroup)) continue OpenGroupUtilities.addGroup(context, openGroup, 1) } + if (configurationMessage.profileKey.isNotEmpty()) { + val profileKey = Base64.encodeBytes(configurationMessage.profileKey) + TextSecurePreferences.setProfileKey(context, profileKey) + } + if (!configurationMessage.profilePicture.isNullOrEmpty()) { + TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture) + } + if (configurationMessage.displayName.isNotEmpty()) { + TextSecurePreferences.setProfileName(context, configurationMessage.displayName) + } + val threadDatabase = DatabaseFactory.getThreadDatabase(context) + val recipientDatabase = DatabaseFactory.getRecipientDatabase(context) + for (contact in configurationMessage.contacts) { + val address = Address.fromSerialized(contact.publicKey) + val recipient = Recipient.from(context, address, true) + if (!contact.profilePicture.isNullOrEmpty()) { + recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) + } + if (contact.profileKey?.isNotEmpty() == true) { + recipientDatabase.setProfileKey(recipient, contact.profileKey) + } + if (contact.name.isNotEmpty()) { + recipientDatabase.setProfileName(recipient, contact.name) + } + recipientDatabase.setProfileSharing(recipient, true) + // create Thread if needed + threadDatabase.getOrCreateThreadIdFor(recipient) + } + if (configurationMessage.contacts.isNotEmpty()) { + threadDatabase.notifyUpdatedFromConfig() + } // TODO: handle new configuration message fields or handle in new pipeline TextSecurePreferences.setConfigurationMessageSynced(context, true) } diff --git a/build.gradle b/build.gradle index 27ab3189b..2730f6910 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ allprojects { } project.ext { + kotlin_version = "1.4.31" androidBuildToolsVersion = '29.0.3' androidCompileSdkVersion = 29 // This is also our target SDK. androidMinSdkVersion = 21 diff --git a/libsession/build.gradle b/libsession/build.gradle index 0f2b5c21d..5fc9b4d4b 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -38,7 +38,7 @@ dependencies { // Remote: implementation 'org.greenrobot:eventbus:3.0.0' implementation "com.goterl.lazycode:lazysodium-android:4.2.0@aar" - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' @@ -61,10 +61,10 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" implementation "org.threeten:threetenbp:1.3.6" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" testImplementation "junit:junit:3.8.2" diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index 846413ce9..ecb56edf0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -123,7 +123,8 @@ class ConfigurationMessage(val closedGroups: List, val openGroups: val displayName = configurationProto.displayName val profilePicture = configurationProto.profilePicture val profileKey = configurationProto.profileKey - return ConfigurationMessage(closedGroups, openGroups, listOf(), displayName, profilePicture, profileKey.toByteArray()) + val contacts = configurationProto.contactsList.mapNotNull { Contact.fromProto(it) } + return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey.toByteArray()) } } diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 79f7a1af9..1bf74da07 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -7,6 +7,9 @@ import android.preference.PreferenceManager.getDefaultSharedPreferences import android.provider.Settings import androidx.annotation.ArrayRes import androidx.core.app.NotificationCompat +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import org.session.libsession.R import org.session.libsession.utilities.preferences.NotificationPrivacyPreference import org.session.libsignal.utilities.logging.Log @@ -16,6 +19,9 @@ import java.util.* object TextSecurePreferences { private val TAG = TextSecurePreferences::class.simpleName + private val _events = MutableSharedFlow(0, 64, BufferOverflow.DROP_OLDEST) + val events get() = _events.asSharedFlow() + const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase" const val THEME_PREF = "pref_theme" const val LANGUAGE_PREF = "pref_language" @@ -321,6 +327,7 @@ object TextSecurePreferences { @JvmStatic fun setProfileName(context: Context, name: String?) { setStringPreference(context, PROFILE_NAME_PREF, name) + _events.tryEmit(PROFILE_NAME_PREF) } @JvmStatic