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 99771cea95..b6ac1f2808 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import com.goterl.lazysodium.utils.KeyPair +import dagger.hilt.android.qualifiers.ApplicationContext import java.security.MessageDigest import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED @@ -85,13 +86,15 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol import javax.inject.Inject +import javax.inject.Singleton import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember private const val TAG = "Storage" +@Singleton open class Storage @Inject constructor( - context: Context, + @ApplicationContext context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory, private val jobDatabase: SessionJobDatabase, diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt index 597b8e5f20..ba899527d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt @@ -9,6 +9,7 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.ConfigFactoryProtocol @@ -17,6 +18,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Toaster import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.thoughtcrime.securesms.database.LokiAPIDatabase +import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.groups.GroupManagerV2Impl import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.repository.DefaultConversationRepository @@ -42,8 +44,6 @@ abstract class AppModule { @Binds abstract fun bindConfigFactory(configFactory: ConfigFactory): ConfigFactoryProtocol - @Binds - abstract fun bindLokiAPIDatabaseProtocol(lokiAPIDatabase: LokiAPIDatabase): LokiAPIDatabaseProtocol } @Module diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseBindings.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseBindings.kt index 5a1dee052a..f42c3ccd06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseBindings.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseBindings.kt @@ -5,6 +5,8 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.session.libsession.database.StorageProtocol +import org.session.libsignal.database.LokiAPIDatabaseProtocol +import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.Storage @Module @@ -14,4 +16,6 @@ abstract class DatabaseBindings { @Binds abstract fun bindStorageProtocol(storage: Storage): StorageProtocol + @Binds + abstract fun bindLokiAPIDatabaseProtocol(lokiAPIDatabase: LokiAPIDatabase): LokiAPIDatabaseProtocol } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt index 7ec4365ebb..ad5eac883f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt @@ -17,7 +17,7 @@ interface DatabaseComponent { companion object { @JvmStatic - @Deprecated("Should use a properly DI instance of components") + @Deprecated("Use Hilt to inject your dependencies instead") fun get(context: Context) = ApplicationContext.getInstance(context).databaseComponent } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index d49f0f8a49..be6eac3db7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -8,11 +8,8 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle -import android.text.format.DateUtils import android.widget.Toast import androidx.activity.viewModels -import androidx.annotation.PluralsRes -import androidx.annotation.StringRes import androidx.core.os.bundleOf import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -20,6 +17,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -34,69 +33,50 @@ import network.loki.messenger.databinding.ActivityHomeBinding import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.LibSessionGroupLeavingJob -import org.session.libsession.messaging.sending_receiving.MessageSender -import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.ProfilePictureModifiedEvent -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.ThreadUtils -import org.session.libsignal.utilities.toHexString -import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.start.StartConversationFragment import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.SessionJobDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter import org.thoughtcrime.securesms.home.search.GlobalSearchInputLayout import org.thoughtcrime.securesms.home.search.GlobalSearchResult import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity -import com.bumptech.glide.Glide -import com.bumptech.glide.RequestManager -import org.session.libsession.messaging.groups.GroupManagerV2 -import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.SettingsActivity import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity import org.thoughtcrime.securesms.showMuteDialog import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.setThemedContent -import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.IP2Country -import org.thoughtcrime.securesms.util.RelativeDay import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.start -import java.io.IOException -import java.util.Calendar -import java.util.Locale import javax.inject.Inject -import kotlin.math.abs -import kotlin.time.Duration.Companion.days -import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds // Intent extra keys so we know where we came from private const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT" @@ -120,6 +100,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), @Inject lateinit var textSecurePreferences: TextSecurePreferences @Inject lateinit var configFactory: ConfigFactory @Inject lateinit var groupManagerV2: GroupManagerV2 + @Inject lateinit var lokiThreadDatabase: LokiThreadDatabase + @Inject lateinit var sessionJobDatabase: SessionJobDatabase + @Inject lateinit var clock: SnodeClock private val globalSearchViewModel by viewModels() private val homeViewModel by viewModels() @@ -460,7 +443,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } else if (thread.recipient.isCommunityRecipient) { val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) - val openGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit + val openGroup = lokiThreadDatabase.getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager @@ -586,7 +569,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun markAllAsRead(thread: ThreadRecord) { lifecycleScope.launch(Dispatchers.Default) { - MessagingModuleConfiguration.shared.storage.markConversationAsRead(thread.threadId, SnodeAPI.nowWithOffset) + storage.markConversationAsRead(thread.threadId, clock.currentTimeMills()) } } @@ -643,11 +626,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), lifecycleScope.launch(Dispatchers.Main) { val context = this@HomeActivity // Cancel any outstanding jobs - DatabaseComponent.get(context).sessionJobDatabase() + sessionJobDatabase .cancelPendingMessageSendJobs(threadID) // Delete the conversation - val v2OpenGroup = DatabaseComponent.get(context).lokiThreadDatabase() + val v2OpenGroup = lokiThreadDatabase .getOpenGroupChat(threadID) if (v2OpenGroup != null) { OpenGroupManager.delete( diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index a22b827e07..46dbc7d811 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -22,6 +22,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.SnodeClock import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil @@ -43,7 +44,6 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import javax.inject.Inject interface ConversationRepository { @@ -90,6 +90,7 @@ class DefaultConversationRepository @Inject constructor( private val configFactory: ConfigFactory, private val contentResolver: ContentResolver, private val groupManager: GroupManagerV2, + private val clock: SnodeClock, ) : ConversationRepository { override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? { @@ -134,13 +135,13 @@ class DefaultConversationRepository @Inject constructor( val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return for (contact in contacts) { val message = VisibleMessage() - message.sentTimestamp = SnodeAPI.nowWithOffset + message.sentTimestamp = clock.currentTimeMills() val openGroupInvitation = OpenGroupInvitation().apply { name = openGroup.name url = openGroup.joinURL } message.openGroupInvitation = openGroupInvitation - val expirationConfig = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(contact).let(storage::getExpirationConfiguration) + val expirationConfig = threadDb.getOrCreateThreadIdFor(contact).let(storage::getExpirationConfiguration) val expiresInMillis = expirationConfig?.expiryMode?.expiryMillis ?: 0 val expireStartedAt = if (expirationConfig?.expiryMode is ExpiryMode.AfterSend) message.sentTimestamp!! else 0 val outgoingTextMessage = OutgoingTextMessage.fromOpenGroupInvitation( 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 4cff0f87c3..b11a05c5fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -1,34 +1,39 @@ package org.thoughtcrime.securesms.sskenvironment import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext import network.loki.messenger.libsession_util.util.UserPic +import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix -import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.SessionContactDatabase +import org.thoughtcrime.securesms.database.SessionJobDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton class ProfileManager @Inject constructor( - @ApplicationContext private val context: Context, - private val configFactory: ConfigFactory + private val configFactory: ConfigFactoryProtocol, + private val storage: StorageProtocol, + private val contactDatabase: SessionContactDatabase, + private val recipientDatabase: RecipientDatabase, + private val jobDatabase: SessionJobDatabase, + private val preferences: TextSecurePreferences, ) : SSKEnvironment.ProfileManagerProtocol { override fun setNickname(context: Context, recipient: Recipient, nickname: String?) { if (recipient.isLocalNumber) return val accountID = recipient.address.serialize() - val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase() var contact = contactDatabase.getContactWithAccountID(accountID) if (contact == null) contact = Contact(accountID) - contact.threadID = DatabaseComponent.get(context).storage().getThreadId(recipient.address) + contact.threadID = storage.getThreadId(recipient.address) if (contact.nickname != nickname) { contact.nickname = nickname contactDatabase.setContact(contact) @@ -40,17 +45,15 @@ class ProfileManager @Inject constructor( // New API if (recipient.isLocalNumber) return val accountID = recipient.address.serialize() - val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase() var contact = contactDatabase.getContactWithAccountID(accountID) if (contact == null) contact = Contact(accountID) - contact.threadID = DatabaseComponent.get(context).storage().getThreadId(recipient.address) + contact.threadID = storage.getThreadId(recipient.address) if (contact.name != name) { contact.name = name contactDatabase.setContact(contact) } // Old API - val database = DatabaseComponent.get(context).recipientDatabase() - database.setProfileName(recipient, name) + recipientDatabase.setProfileName(recipient, name) recipient.notifyListeners() contactUpdatedInternal(contact) } @@ -61,23 +64,20 @@ class ProfileManager @Inject constructor( profilePictureURL: String?, profileKey: ByteArray? ) { - val hasPendingDownload = DatabaseComponent - .get(context) - .sessionJobDatabase() + val hasPendingDownload = jobDatabase .getAllJobs(RetrieveProfileAvatarJob.KEY).any { (it.value as? RetrieveProfileAvatarJob)?.recipientAddress == recipient.address } val resolved = recipient.resolve() - DatabaseComponent.get(context).storage().setProfilePicture( + storage.setProfilePicture( recipient = resolved, newProfileKey = profileKey, newProfilePicture = profilePictureURL ) val accountID = recipient.address.serialize() - val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase() var contact = contactDatabase.getContactWithAccountID(accountID) if (contact == null) contact = Contact(accountID) - contact.threadID = DatabaseComponent.get(context).storage().getThreadId(recipient.address) + contact.threadID = storage.getThreadId(recipient.address) if (!contact.profilePictureEncryptionKey.contentEquals(profileKey) || contact.profilePictureURL != profilePictureURL) { contact.profilePictureEncryptionKey = profileKey contact.profilePictureURL = profilePictureURL @@ -91,12 +91,11 @@ class ProfileManager @Inject constructor( } override fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) { - val database = DatabaseComponent.get(context).recipientDatabase() - database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) + recipientDatabase.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) } override fun contactUpdatedInternal(contact: Contact): String? { - if (contact.accountID == TextSecurePreferences.getLocalNumber(context)) return null + if (contact.accountID == preferences.getLocalNumber()) return null val accountId = AccountId(contact.accountID) if (accountId.prefix != IdPrefix.STANDARD) return null // only internally store standard account IDs return configFactory.withMutableUserConfigs {