diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index dd6fe3874c..b3af81182a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -153,6 +153,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr DatabaseSecretProvider.getOrCreateDatabaseSecret(this), AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret()); }) + .addBlocking("signal-store", () -> SignalStore.init(this)) .addBlocking("logging", () -> { initializeLogging(); Log.i(TAG, "onCreate()"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index 99be04e0aa..32af07859c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -188,7 +188,7 @@ object BackupRepository { } val db = KeyValueDatabase.createWithName(context, "$baseName.db") - SignalStore(KeyValueStore(db)) + SignalStore(context, KeyValueStore(db)) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt index f7f312c997..fb0de78932 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.emoji import android.net.Uri +import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import org.thoughtcrime.securesms.components.emoji.Emoji import org.thoughtcrime.securesms.components.emoji.EmojiPageModel @@ -122,7 +123,8 @@ class EmojiSource( } } - private fun loadAssetBasedEmojis(): EmojiSource { + @VisibleForTesting + fun loadAssetBasedEmojis(): EmojiSource { val emojiData: InputStream = AppDependencies.application.assets.open("emoji/emoji_data.json") emojiData.use { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt index b04a87ea22..373c554e10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import android.preference.PreferenceManager -import androidx.annotation.VisibleForTesting import org.signal.core.util.Base64 import org.signal.core.util.logging.Log import org.signal.libsignal.protocol.IdentityKey @@ -73,17 +72,10 @@ class AccountValues internal constructor(store: KeyValueStore) : SignalStoreValu private const val KEY_USERNAME_SYNC_STATE = "phoneNumberPrivacy.usernameSyncState" private const val KEY_USERNAME_SYNC_ERROR_COUNT = "phoneNumberPrivacy.usernameErrorCount" - @VisibleForTesting - const val KEY_E164 = "account.e164" - - @VisibleForTesting - const val KEY_ACI = "account.aci" - - @VisibleForTesting - const val KEY_PNI = "account.pni" - - @VisibleForTesting - const val KEY_IS_REGISTERED = "account.is_registered" + private const val KEY_E164 = "account.e164" + private const val KEY_ACI = "account.aci" + private const val KEY_PNI = "account.pni" + private const val KEY_IS_REGISTERED = "account.is_registered" } init { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/NotificationProfileValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/NotificationProfileValues.kt index f79dac6543..3111d4e0fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/NotificationProfileValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/NotificationProfileValues.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.keyvalue -import androidx.annotation.VisibleForTesting - /** * Values for managing enable/disable state and corresponding alerts for Notification Profiles. */ @@ -12,14 +10,9 @@ class NotificationProfileValues(store: KeyValueStore) : SignalStoreValues(store) private const val KEY_LAST_PROFILE_POPUP_TIME = "np.last_profile_popup_time" private const val KEY_SEEN_TOOLTIP = "np.seen_tooltip" - @VisibleForTesting - const val KEY_MANUALLY_ENABLED_PROFILE = "np.manually_enabled_profile" - - @VisibleForTesting - const val KEY_MANUALLY_ENABLED_UNTIL = "np.manually_enabled_until" - - @VisibleForTesting - const val KEY_MANUALLY_DISABLED_AT = "np.manually_disabled_at" + private const val KEY_MANUALLY_ENABLED_PROFILE = "np.manually_enabled_profile" + private const val KEY_MANUALLY_ENABLED_UNTIL = "np.manually_enabled_until" + private const val KEY_MANUALLY_DISABLED_AT = "np.manually_disabled_at" } public override fun onFirstEverAppLaunch() = Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt index 7324cc8858..b341d5ddd7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.keyvalue -import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -33,6 +32,7 @@ class PaymentsValues internal constructor(store: KeyValueStore) : SignalStoreVal companion object { private val TAG = Log.tag(PaymentsValues::class.java) + private const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled" private const val PAYMENTS_ENTROPY = "payments_entropy" private const val MOB_LEDGER = "mob_ledger" private const val PAYMENTS_CURRENT_CURRENCY = "payments_current_currency" @@ -50,9 +50,6 @@ class PaymentsValues internal constructor(store: KeyValueStore) : SignalStoreVal private const val SHOW_SAVE_RECOVERY_PHRASE = "mob_show_save_recovery_phrase" private val LARGE_BALANCE_THRESHOLD = Money.mobileCoin(BigDecimal.valueOf(500)) - - @VisibleForTesting - const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled" } @get:JvmName("isPaymentLockEnabled") diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.kt index 9ab8c67656..7b08aa51f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.kt @@ -1,15 +1,14 @@ package org.thoughtcrime.securesms.keyvalue +import android.app.Application import androidx.annotation.VisibleForTesting import androidx.preference.PreferenceDataStore -import org.signal.core.util.ResettableLazy import org.thoughtcrime.securesms.database.KeyValueDatabase -import org.thoughtcrime.securesms.dependencies.AppDependencies.application /** * Simple, encrypted key-value store. */ -class SignalStore(private val store: KeyValueStore) { +class SignalStore(context: Application, private val store: KeyValueStore) { val accountValues = AccountValues(store) val svrValues = SvrValues(store) @@ -39,15 +38,22 @@ class SignalStore(private val store: KeyValueStore) { val apkUpdateValues = ApkUpdateValues(store) val backupValues = BackupValues(store) - val plainTextValues = PlainTextSharedPrefsDataStore(application) + val plainTextValues = PlainTextSharedPrefsDataStore(context) companion object { - private var instanceOverride: SignalStore? = null - private val _instance = ResettableLazy { - instanceOverride ?: SignalStore(KeyValueStore(KeyValueDatabase.getInstance(application))) + private var instance: SignalStore? = null + + @JvmStatic + fun init(context: Application) { + if (instance == null) { + synchronized(SignalStore::class.java) { + if (instance == null) { + instance = SignalStore(context, KeyValueStore(KeyValueDatabase.getInstance(context))) + } + } + } } - private val instance by _instance @JvmStatic fun onFirstEverAppLaunch() { @@ -114,7 +120,7 @@ class SignalStore(private val store: KeyValueStore) { */ @VisibleForTesting fun resetCache() { - instance.store.resetCache() + instance!!.store.resetCache() } /** @@ -122,144 +128,144 @@ class SignalStore(private val store: KeyValueStore) { */ @JvmStatic fun onPostBackupRestore() { - instance.store.resetCache() + instance!!.store.resetCache() } @JvmStatic @get:JvmName("account") val account: AccountValues - get() = instance.accountValues + get() = instance!!.accountValues @JvmStatic @get:JvmName("svr") val svr: SvrValues - get() = instance.svrValues + get() = instance!!.svrValues @JvmStatic @get:JvmName("registration") val registration: RegistrationValues - get() = instance.registrationValues + get() = instance!!.registrationValues @JvmStatic @get:JvmName("pin") val pin: PinValues - get() = instance.pinValues + get() = instance!!.pinValues val remoteConfig: RemoteConfigValues - get() = instance.remoteConfigValues + get() = instance!!.remoteConfigValues @JvmStatic @get:JvmName("storageService") val storageService: StorageServiceValues - get() = instance.storageServiceValues + get() = instance!!.storageServiceValues @JvmStatic @get:JvmName("uiHints") val uiHints: UiHintValues - get() = instance.uiHintValues + get() = instance!!.uiHintValues @JvmStatic @get:JvmName("tooltips") val tooltips: TooltipValues - get() = instance.tooltipValues + get() = instance!!.tooltipValues @JvmStatic @get:JvmName("misc") val misc: MiscellaneousValues - get() = instance.miscValues + get() = instance!!.miscValues @JvmStatic @get:JvmName("internal") val internal: InternalValues - get() = instance.internalValues + get() = instance!!.internalValues @JvmStatic @get:JvmName("emoji") val emoji: EmojiValues - get() = instance.emojiValues + get() = instance!!.emojiValues @JvmStatic @get:JvmName("settings") val settings: SettingsValues - get() = instance.settingsValues + get() = instance!!.settingsValues @JvmStatic @get:JvmName("certificate") val certificate: CertificateValues - get() = instance.certificateValues + get() = instance!!.certificateValues @JvmStatic @get:JvmName("phoneNumberPrivacy") val phoneNumberPrivacy: PhoneNumberPrivacyValues - get() = instance.phoneNumberPrivacyValues + get() = instance!!.phoneNumberPrivacyValues @JvmStatic @get:JvmName("onboarding") val onboarding: OnboardingValues - get() = instance.onboardingValues + get() = instance!!.onboardingValues @JvmStatic @get:JvmName("wallpaper") val wallpaper: WallpaperValues - get() = instance.wallpaperValues + get() = instance!!.wallpaperValues @JvmStatic @get:JvmName("payments") val payments: PaymentsValues - get() = instance.paymentsValues + get() = instance!!.paymentsValues @JvmStatic @get:JvmName("inAppPayments") val inAppPayments: InAppPaymentValues - get() = instance.inAppPaymentValues + get() = instance!!.inAppPaymentValues @JvmStatic @get:JvmName("proxy") val proxy: ProxyValues - get() = instance.proxyValues + get() = instance!!.proxyValues @JvmStatic @get:JvmName("rateLimit") val rateLimit: RateLimitValues - get() = instance.rateLimitValues + get() = instance!!.rateLimitValues @JvmStatic @get:JvmName("chatColors") val chatColors: ChatColorsValues - get() = instance.chatColorsValues + get() = instance!!.chatColorsValues val imageEditor: ImageEditorValues - get() = instance.imageEditorValues + get() = instance!!.imageEditorValues val notificationProfile: NotificationProfileValues - get() = instance.notificationProfileValues + get() = instance!!.notificationProfileValues @JvmStatic @get:JvmName("releaseChannel") val releaseChannel: ReleaseChannelValues - get() = instance.releaseChannelValues + get() = instance!!.releaseChannelValues @JvmStatic @get:JvmName("story") val story: StoryValues - get() = instance.storyValues + get() = instance!!.storyValues val apkUpdate: ApkUpdateValues - get() = instance.apkUpdateValues + get() = instance!!.apkUpdateValues @JvmStatic @get:JvmName("backup") val backup: BackupValues - get() = instance.backupValues + get() = instance!!.backupValues val groupsV2AciAuthorizationCache: GroupsV2AuthorizationSignalStoreCache - get() = GroupsV2AuthorizationSignalStoreCache.createAciCache(instance.store) + get() = GroupsV2AuthorizationSignalStoreCache.createAciCache(instance!!.store) val plaintext: PlainTextSharedPrefsDataStore - get() = instance.plainTextValues + get() = instance!!.plainTextValues fun getPreferenceDataStore(): PreferenceDataStore { - return SignalPreferenceDataStore(instance.store) + return SignalPreferenceDataStore(instance!!.store) } /** @@ -268,25 +274,7 @@ class SignalStore(private val store: KeyValueStore) { */ @JvmStatic fun blockUntilAllWritesFinished() { - instance.store.blockUntilAllWritesFinished() - } - - /** - * Allows you to set a custom KeyValueStore to read from. Only for testing! - */ - @VisibleForTesting - fun testInject(store: KeyValueStore) { - instanceOverride = SignalStore(store) - _instance.reset() - } - - /** - * Allows you to set a custom SignalStore to read from. Only for testing! - */ - @VisibleForTesting - fun testInject(store: SignalStore) { - instanceOverride = store - _instance.reset() + instance!!.store.blockUntilAllWritesFinished() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt index bbdb15fbcd..d218c61a99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt @@ -5,6 +5,7 @@ package org.thoughtcrime.securesms.providers +import android.app.Application import android.content.ContentUris import android.content.ContentValues import android.content.Intent @@ -15,10 +16,16 @@ import android.net.Uri import android.os.ParcelFileDescriptor import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BuildConfig +import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider +import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientCreator import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.AdaptiveBitmapMetrics @@ -54,6 +61,21 @@ class AvatarProvider : BaseContentProvider() { } } + private fun init(): Application? { + val application = context as? ApplicationContext ?: return null + + SqlCipherLibraryLoader.load() + SignalDatabase.init( + application, + DatabaseSecretProvider.getOrCreateDatabaseSecret(application), + AttachmentSecretProvider.getInstance(application).getOrCreateAttachmentSecret() + ) + + SignalStore.init(application) + + return application + } + override fun onCreate(): Boolean { if (VERBOSE) Log.i(TAG, "onCreate called") return true @@ -63,7 +85,9 @@ class AvatarProvider : BaseContentProvider() { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { if (VERBOSE) Log.i(TAG, "openFile() called!") - if (KeyCachingService.isLocked(context)) { + val application = init() ?: return null + + if (KeyCachingService.isLocked(application)) { Log.w(TAG, "masterSecret was null, abandoning.") return null } @@ -76,7 +100,7 @@ class AvatarProvider : BaseContentProvider() { if (uriMatcher.match(uri) == AVATAR) { if (VERBOSE) Log.i(TAG, "Loading avatar.") try { - val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null + val recipient = getRecipientId(uri)?.let { RecipientCreator.forRecord(application, SignalDatabase.recipients.getRecord(it)) } ?: return null return getParcelFileDescriptorForAvatar(recipient) } catch (ioe: IOException) { Log.w(TAG, ioe) @@ -91,6 +115,8 @@ class AvatarProvider : BaseContentProvider() { override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? { if (VERBOSE) Log.i(TAG, "query() called: $uri") + val application = init() ?: return null + if (SignalDatabase.instance == null) { Log.w(TAG, "SignalDatabase unavailable") return null @@ -99,8 +125,8 @@ class AvatarProvider : BaseContentProvider() { if (uriMatcher.match(uri) == AVATAR) { val recipientId = getRecipientId(uri) ?: return null - if (AvatarHelper.hasAvatar(context!!, recipientId)) { - val file: File = AvatarHelper.getAvatarFile(context!!, recipientId) + if (AvatarHelper.hasAvatar(application, recipientId)) { + val file: File = AvatarHelper.getAvatarFile(application, recipientId) if (file.exists()) { return createCursor(projection, file.name, file.length()) } @@ -115,6 +141,8 @@ class AvatarProvider : BaseContentProvider() { override fun getType(uri: Uri): String? { if (VERBOSE) Log.i(TAG, "getType() called: $uri") + init() ?: return null + if (SignalDatabase.instance == null) { Log.w(TAG, "SignalDatabase unavailable") return null diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java index 80330fbbff..f5b7b61c17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -9,24 +9,15 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; -import com.annimon.stream.Stream; - import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.conversation.colors.AvatarColor; -import org.thoughtcrime.securesms.database.CallLinkTable; import org.thoughtcrime.securesms.database.DistributionListTables; import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.model.GroupRecord; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.model.DistributionListRecord; -import org.thoughtcrime.securesms.database.model.RecipientRecord; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; -import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; @@ -198,62 +189,11 @@ public final class LiveRecipient { } private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) { - RecipientRecord record = recipientTable.getRecord(id); - - Recipient recipient; - if (record.getGroupId() != null) { - recipient = getGroupRecipientDetails(record); - } else if (record.getDistributionListId() != null) { - recipient = getDistributionListRecipientDetails(record); - } else if (record.getCallLinkRoomId() != null) { - recipient = getCallLinkRecipientDetails(record); - } else { - recipient = RecipientCreator.forIndividual(context, record); - } - + Recipient recipient = RecipientCreator.forRecord(context, recipientTable.getRecord(id)); RecipientIdCache.INSTANCE.put(recipient); return recipient; } - @WorkerThread - private @NonNull Recipient getGroupRecipientDetails(@NonNull RecipientRecord record) { - Optional groupRecord = groupDatabase.getGroup(record.getId()); - - if (groupRecord.isPresent()) { - return RecipientCreator.forGroup(groupRecord.get(), record); - } else { - return RecipientCreator.forUnknownGroup(record.getId(), record.getGroupId()); - } - } - - @WorkerThread - private @NonNull Recipient getDistributionListRecipientDetails(@NonNull RecipientRecord record) { - DistributionListRecord groupRecord = distributionListTables.getList(Objects.requireNonNull(record.getDistributionListId())); - - // TODO [stories] We'll have to see what the perf is like for very large distribution lists. We may not be able to support fetching all the members. - if (groupRecord != null) { - String title = groupRecord.isUnknown() ? null : groupRecord.getName(); - List members = Stream.of(groupRecord.getMembers()).filterNot(RecipientId::isUnknown).toList(); - - return RecipientCreator.forDistributionList(title, members, record); - } - - return RecipientCreator.forDistributionList(null, null, record); - } - - @WorkerThread - private @NonNull Recipient getCallLinkRecipientDetails(@NonNull RecipientRecord record) { - CallLinkTable.CallLink callLink = SignalDatabase.callLinks().getCallLinkByRoomId(Objects.requireNonNull(record.getCallLinkRoomId())); - - if (callLink != null) { - String name = callLink.getState().getName(); - - return RecipientCreator.forCallLink(name, record, callLink.getAvatarColor()); - } - - return RecipientCreator.forCallLink(null, record, AvatarColor.UNKNOWN); - } - synchronized void set(@NonNull Recipient recipient) { this.recipient.set(recipient); this.liveData.postValue(recipient); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientCreator.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientCreator.kt index e689855f8c..b3c5ad4574 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientCreator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientCreator.kt @@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.recipients import android.content.Context import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState +import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.GroupRecord import org.thoughtcrime.securesms.database.model.RecipientRecord import org.thoughtcrime.securesms.groups.GroupId @@ -101,6 +103,22 @@ object RecipientCreator { ) } + @JvmStatic + @WorkerThread + fun forRecord(context: Context, record: RecipientRecord): Recipient { + val recipient = if (record.groupId != null) { + getGroupRecipientDetails(record) + } else if (record.distributionListId != null) { + getDistributionListRecipientDetails(record) + } else if (record.callLinkRoomId != null) { + getCallLinkRecipientDetails(record) + } else { + forIndividual(context, record) + } + + return recipient + } + @JvmStatic fun forUnknown(): Recipient { return Recipient.UNKNOWN @@ -186,4 +204,43 @@ object RecipientCreator { note = record.note ) } + + @WorkerThread + private fun getGroupRecipientDetails(record: RecipientRecord): Recipient { + val groupRecord = SignalDatabase.groups.getGroup(record.id) + + return if (groupRecord.isPresent) { + forGroup(groupRecord.get(), record) + } else { + forUnknownGroup(record.id, record.groupId) + } + } + + @WorkerThread + private fun getDistributionListRecipientDetails(record: RecipientRecord): Recipient { + val groupRecord = SignalDatabase.distributionLists.getList(record.distributionListId!!) + + // TODO [stories] We'll have to see what the perf is like for very large distribution lists. We may not be able to support fetching all the members. + if (groupRecord != null) { + val title = if (groupRecord.isUnknown) null else groupRecord.name + val members = groupRecord.members.filterNot { it.isUnknown } + + return forDistributionList(title, members, record) + } + + return forDistributionList(null, null, record) + } + + @WorkerThread + private fun getCallLinkRecipientDetails(record: RecipientRecord): Recipient { + val callLink = SignalDatabase.callLinks.getCallLinkByRoomId(record.callLinkRoomId!!) + + if (callLink != null) { + val name = callLink.state.name + + return forCallLink(name, record, callLink.avatarColor) + } + + return forCallLink(null, record, AvatarColor.UNKNOWN) + } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/SignalStoreRule.kt b/app/src/test/java/org/thoughtcrime/securesms/SignalStoreRule.kt deleted file mode 100644 index 2ab3531656..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/SignalStoreRule.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.thoughtcrime.securesms - -import androidx.test.core.app.ApplicationProvider -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import org.thoughtcrime.securesms.dependencies.AppDependencies -import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider -import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet -import org.thoughtcrime.securesms.keyvalue.KeyValueStore -import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage -import org.thoughtcrime.securesms.keyvalue.SignalStore - -/** - * Rule to setup [SignalStore] with a mock [KeyValueDataSet]. Must be used with Roboelectric. - * - * Can provide [defaultValues] to set the same values before each test and use [dataSet] directly to add any - * test specific values. - * - * The [dataSet] is reset at the beginning of each test to an empty state. - */ -class SignalStoreRule @JvmOverloads constructor(private val defaultValues: KeyValueDataSet.() -> Unit = {}) : TestRule { - var dataSet = KeyValueDataSet() - private set - - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { - @Throws(Throwable::class) - override fun evaluate() { - if (!AppDependencies.isInitialized) { - AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) - } - - dataSet = KeyValueDataSet() - SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet))) - defaultValues.invoke(dataSet) - - base.evaluate() - } - } - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/emoji/EmojiUtilTest_isEmoji.kt b/app/src/test/java/org/thoughtcrime/securesms/components/emoji/EmojiUtilTest_isEmoji.kt index f13bac171f..d2b850f17c 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/emoji/EmojiUtilTest_isEmoji.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/components/emoji/EmojiUtilTest_isEmoji.kt @@ -2,50 +2,34 @@ package org.thoughtcrime.securesms.components.emoji import android.app.Application import androidx.test.core.app.ApplicationProvider +import io.mockk.every +import io.mockk.mockkObject import org.junit.Assert -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.mockito.Mock -import org.mockito.MockedStatic -import org.mockito.Mockito -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule import org.robolectric.ParameterizedRobolectricTestRunner import org.robolectric.annotation.Config -import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider import org.thoughtcrime.securesms.dependencies.AppDependencies -import org.thoughtcrime.securesms.emoji.EmojiSource.Companion.refresh -import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet -import org.thoughtcrime.securesms.keyvalue.KeyValueStore -import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage -import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider +import org.thoughtcrime.securesms.emoji.EmojiSource @RunWith(ParameterizedRobolectricTestRunner::class) @Config(manifest = Config.NONE, application = Application::class) class EmojiUtilTest_isEmoji(private val input: String?, private val output: Boolean) { - @Rule - @JvmField - val rule: MockitoRule = MockitoJUnit.rule() - - @Mock - private val applicationDependenciesMockedStatic: MockedStatic? = null - - @Mock - private val attachmentSecretProviderMockedStatic: MockedStatic? = null @Throws(Exception::class) @Test fun isEmoji() { - val application = ApplicationProvider.getApplicationContext() + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } - Mockito.`when`(AppDependencies.application).thenReturn(application) - Mockito.`when`(AttachmentSecretProvider.getInstance(ArgumentMatchers.any())).thenThrow(RuntimeException::class.java) - SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(KeyValueDataSet()))) - refresh() + val source = EmojiSource.loadAssetBasedEmojis() - Assert.assertEquals(output, EmojiUtil.isEmoji(input)) + mockkObject(EmojiSource) { + every { EmojiSource.latest } returns source + Assert.assertEquals(output, EmojiUtil.isEmoji(input)) + } } companion object { diff --git a/app/src/test/java/org/thoughtcrime/securesms/crash/CrashConfigTest.kt b/app/src/test/java/org/thoughtcrime/securesms/crash/CrashConfigTest.kt index b300ee924c..0a6b113a2c 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/crash/CrashConfigTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/crash/CrashConfigTest.kt @@ -4,6 +4,8 @@ import android.app.Application import androidx.test.core.app.ApplicationProvider import io.mockk.every import io.mockk.mockkObject +import io.mockk.unmockkAll +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -12,12 +14,9 @@ import org.robolectric.annotation.Config import org.thoughtcrime.securesms.assertIs import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider -import org.thoughtcrime.securesms.keyvalue.AccountValues -import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet -import org.thoughtcrime.securesms.keyvalue.KeyValueStore -import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.RemoteConfig +import org.whispersystems.signalservice.api.push.ServiceId import java.util.UUID @RunWith(RobolectricTestRunner::class) @@ -32,15 +31,13 @@ class CrashConfigTest { AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) } - val store = KeyValueStore( - MockKeyValuePersistentStorage.withDataSet( - KeyValueDataSet().apply { - putString(AccountValues.KEY_ACI, UUID.randomUUID().toString()) - } - ) - ) + mockkObject(SignalStore) + every { SignalStore.account.aci } returns ServiceId.ACI.from(UUID.randomUUID()) + } - SignalStore.testInject(store) + @After + fun tearDown() { + unmockkAll() } @Test diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt index 54efd17911..182ebdf3e4 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt @@ -6,13 +6,15 @@ import android.app.Application import androidx.test.core.app.ApplicationProvider import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.slot +import io.mockk.unmockkAll import io.mockk.verify import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers import org.hamcrest.Matchers.`is` +import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -28,15 +30,18 @@ import org.signal.storageservice.protos.groups.GroupChangeResponse import org.signal.storageservice.protos.groups.Member import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedMember -import org.thoughtcrime.securesms.SignalStoreRule import org.thoughtcrime.securesms.TestZkGroupServer import org.thoughtcrime.securesms.database.GroupStateTestData import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.model.databaseprotos.member +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testutil.SystemOutLogger +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations @@ -73,12 +78,17 @@ class GroupManagerV2Test_edit { private lateinit var manager: GroupManagerV2 - @get:Rule - val signalStore: SignalStoreRule = SignalStoreRule() - @Suppress("UsePropertyAccessSyntax") @Before fun setUp() { + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } + + mockkObject(RemoteConfig) + mockkObject(SignalStore) + every { RemoteConfig.internalUser } returns false + ThreadUtil.enforceAssertions = false Log.initialize(SystemOutLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) @@ -106,6 +116,11 @@ class GroupManagerV2Test_edit { ) } + @After + fun tearDown() { + unmockkAll() + } + private fun given(init: GroupStateTestData.() -> Unit) { val data = GroupStateTestData(masterKey, groupOperations) data.init() diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt index 334a84efe3..afb4536e66 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessorTest.kt @@ -2,13 +2,14 @@ package org.thoughtcrime.securesms.groups.v2.processing import android.annotation.SuppressLint import android.app.Application +import androidx.test.core.app.ApplicationProvider import io.mockk.every import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.spyk -import io.mockk.unmockkObject +import io.mockk.unmockkAll import io.mockk.unmockkStatic import io.mockk.verify import org.hamcrest.MatcherAssert.assertThat @@ -16,7 +17,6 @@ import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.`is` import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -32,7 +32,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroupChange import org.signal.storageservice.protos.groups.local.DecryptedMember import org.signal.storageservice.protos.groups.local.DecryptedString import org.signal.storageservice.protos.groups.local.DecryptedTimer -import org.thoughtcrime.securesms.SignalStoreRule import org.thoughtcrime.securesms.database.GroupStateTestData import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.RecipientTable @@ -46,6 +45,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.requestingMember import org.thoughtcrime.securesms.database.setNewDescription import org.thoughtcrime.securesms.database.setNewTitle import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupNotAMemberException import org.thoughtcrime.securesms.groups.GroupsV2Authorization @@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor.Pr import org.thoughtcrime.securesms.jobmanager.JobManager import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testutil.SystemOutLogger @@ -100,11 +101,15 @@ class GroupsV2StateProcessorTest { private lateinit var processor: GroupsV2StateProcessor - @get:Rule - val signalStore: SignalStoreRule = SignalStoreRule() - @Before fun setUp() { + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } + + mockkObject(SignalStore) + every { SignalStore.internal.gv2IgnoreP2PChanges() } returns false + Log.initialize(SystemOutLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) @@ -138,11 +143,7 @@ class GroupsV2StateProcessorTest { @After fun tearDown() { - unmockkStatic(AppDependencies::class) - unmockkObject(SignalDatabase) - unmockkObject(ProfileAndMessageHelper) - unmockkStatic(DecryptedGroupUtil::class) - unmockkStatic(Recipient::class) + unmockkAll() } private fun given(init: GroupStateTestData.() -> Unit) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/keyvalue/PaymentsValuesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/keyvalue/PaymentsValuesTest.kt index 702bebc000..4d6c7ed7d2 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/keyvalue/PaymentsValuesTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/keyvalue/PaymentsValuesTest.kt @@ -3,7 +3,10 @@ package org.thoughtcrime.securesms.keyvalue import android.app.Application import androidx.test.core.app.ApplicationProvider import io.mockk.every +import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.unmockkAll +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -18,6 +21,8 @@ import org.thoughtcrime.securesms.util.RemoteConfig @Config(manifest = Config.NONE, application = Application::class) class PaymentsValuesTest { + private lateinit var paymentValues: PaymentsValues + @Before fun setup() { if (!AppDependencies.isInitialized) { @@ -25,29 +30,32 @@ class PaymentsValuesTest { } mockkObject(RemoteConfig) + mockkObject(SignalStore) + + paymentValues = mockk() + every { paymentValues.paymentsAvailability } answers { callOriginal() } + + every { SignalStore.payments } returns paymentValues + + every { SignalStore.account.isRegistered } returns true + } + + @After + fun tearDown() { + unmockkAll() } @Test fun `when unregistered, expect NOT_IN_REGION`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, false) - } - ) + every { SignalStore.account.isRegistered } returns false assertEquals(PaymentsAvailability.NOT_IN_REGION, SignalStore.payments.paymentsAvailability) } @Test fun `when flag disabled and no account, expect DISABLED_REMOTELY`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns false every { RemoteConfig.payments } returns false every { RemoteConfig.paymentsCountryBlocklist } returns "" @@ -56,14 +64,8 @@ class PaymentsValuesTest { @Test fun `when flag disabled but has account, expect WITHDRAW_ONLY`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns true every { RemoteConfig.payments } returns false every { RemoteConfig.paymentsCountryBlocklist } returns "" @@ -72,14 +74,8 @@ class PaymentsValuesTest { @Test fun `when flag enabled and no account, expect REGISTRATION_AVAILABLE`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns false every { RemoteConfig.payments } returns true every { RemoteConfig.paymentsCountryBlocklist } returns "" @@ -88,14 +84,8 @@ class PaymentsValuesTest { @Test fun `when flag enabled and has account, expect WITHDRAW_AND_SEND`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns true every { RemoteConfig.payments } returns true every { RemoteConfig.paymentsCountryBlocklist } returns "" @@ -104,14 +94,8 @@ class PaymentsValuesTest { @Test fun `when flag enabled and no account and in the country blocklist, expect NOT_IN_REGION`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns false every { RemoteConfig.payments } returns true every { RemoteConfig.paymentsCountryBlocklist } returns "1" @@ -120,31 +104,11 @@ class PaymentsValuesTest { @Test fun `when flag enabled and has account and in the country blocklist, expect WITHDRAW_ONLY`() { - setupStore( - KeyValueDataSet().apply { - putBoolean(AccountValues.KEY_IS_REGISTERED, true) - putString(AccountValues.KEY_E164, "+15551234567") - putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true) - } - ) - + every { SignalStore.account.e164 } returns "+15551234567" + every { paymentValues.mobileCoinPaymentsEnabled() } returns true every { RemoteConfig.payments } returns true every { RemoteConfig.paymentsCountryBlocklist } returns "1" assertEquals(PaymentsAvailability.WITHDRAW_ONLY, SignalStore.payments.paymentsAvailability) } - - /** - * Account values will overwrite some values upon first access, so this takes care of that - */ - private fun setupStore(dataset: KeyValueDataSet) { - val store = KeyValueStore( - MockKeyValuePersistentStorage.withDataSet( - dataset.apply { - putString(AccountValues.KEY_ACI, "") - } - ) - ) - SignalStore.testInject(store) - } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt index ebb9725d46..948bcff8f7 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.megaphone import android.app.Application import android.net.Uri +import androidx.test.core.app.ApplicationProvider import io.mockk.clearMocks import io.mockk.every import io.mockk.mockk @@ -15,15 +16,15 @@ import org.junit.After import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import org.thoughtcrime.securesms.SignalStoreRule import org.thoughtcrime.securesms.database.RemoteMegaphoneTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.util.toMillis import java.time.LocalDateTime import java.util.UUID @@ -36,11 +37,11 @@ import java.util.UUID @Config(manifest = Config.NONE, application = Application::class) class RemoteMegaphoneRepositoryTest { - @get:Rule - val signalStore: SignalStoreRule = SignalStoreRule() - @Before fun setUp() { + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } } @After diff --git a/app/src/test/java/org/thoughtcrime/securesms/notifications/profiles/NotificationProfilesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/notifications/profiles/NotificationProfilesTest.kt index 059d7ed51d..5f20e4f7c2 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/notifications/profiles/NotificationProfilesTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/notifications/profiles/NotificationProfilesTest.kt @@ -1,16 +1,24 @@ package org.thoughtcrime.securesms.notifications.profiles import android.app.Application +import androidx.test.core.app.ApplicationProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.nullValue -import org.junit.Rule +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import org.thoughtcrime.securesms.SignalStoreRule +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.keyvalue.NotificationProfileValues +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.toMillis import java.time.DayOfWeek import java.time.LocalDateTime @@ -43,8 +51,26 @@ class NotificationProfilesTest { schedule = NotificationProfileSchedule(2) ) - @get:Rule - val signalStore: SignalStoreRule = SignalStoreRule() + private lateinit var notificationProfileValues: NotificationProfileValues + + @Before + fun setUp() { + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } + + notificationProfileValues = mockk() + every { notificationProfileValues.manuallyEnabledUntil } returns 0 + every { notificationProfileValues.manuallyDisabledAt } returns 0 + + mockkObject(SignalStore) + every { SignalStore.notificationProfile } returns notificationProfileValues + } + + @After + fun tearDown() { + unmockkAll() + } @Test fun `when no profiles then return null`() { @@ -59,9 +85,9 @@ class NotificationProfilesTest { @Test fun `when first is not enabled and second is manually enabled forever then return second`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, 5000L) + every { notificationProfileValues.manuallyEnabledProfile } returns second.id + every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE + every { notificationProfileValues.manuallyDisabledAt } returns 5000L val profiles = listOf(first, second) assertThat("active profile is profile second", NotificationProfiles.getActiveProfile(profiles, 3000L, utc), `is`(profiles[1])) @@ -76,9 +102,9 @@ class NotificationProfilesTest { @Test fun `when first is scheduled and second is manually enabled forever within first's schedule then return second`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns second.id + every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) val profiles = listOf(first.copy(schedule = schedule), second) @@ -87,9 +113,9 @@ class NotificationProfilesTest { @Test fun `when first is scheduled and second is manually enabled forever before first's schedule start then return first`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns second.id + every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, true, start = 900, daysEnabled = setOf(DayOfWeek.SUNDAY)) val profiles = listOf(first.copy(schedule = schedule), second) @@ -108,9 +134,9 @@ class NotificationProfilesTest { @Test fun `when first and second have overlapping schedules and first is created before second and first is manually enabled within overlapping schedule then return first`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns first.id + every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val firstSchedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) val secondSchedule = NotificationProfileSchedule(id = 4L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) @@ -121,9 +147,9 @@ class NotificationProfilesTest { @Test fun `when profile is manually enabled for set time after schedule end and now is after schedule end but before manual then return profile`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday930am.toMillis(ZoneOffset.UTC)) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns first.id + every { notificationProfileValues.manuallyEnabledUntil } returns sunday930am.toMillis(ZoneOffset.UTC) + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 845, daysEnabled = setOf(DayOfWeek.SUNDAY)) val profiles = listOf(first.copy(schedule = schedule)) @@ -132,9 +158,9 @@ class NotificationProfilesTest { @Test fun `when profile is manually enabled for set time before schedule end and now is after manual but before schedule end then return null`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC)) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns first.id + every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.toMillis(ZoneOffset.UTC) + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 1000, daysEnabled = setOf(DayOfWeek.SUNDAY)) val profiles = listOf(first.copy(schedule = schedule)) @@ -143,9 +169,9 @@ class NotificationProfilesTest { @Test fun `when profile is manually enabled yesterday and is scheduled also for today then return profile`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC)) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns first.id + every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.toMillis(ZoneOffset.UTC) + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 700, end = 900, daysEnabled = setOf(DayOfWeek.SUNDAY, DayOfWeek.MONDAY)) val profiles = listOf(first.copy(schedule = schedule)) @@ -154,9 +180,9 @@ class NotificationProfilesTest { @Test fun `when profile is manually disabled and schedule is on but with start after end and now is before end then return null`() { - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, 0) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, 0) - signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) + every { notificationProfileValues.manuallyEnabledProfile } returns 0 + every { notificationProfileValues.manuallyEnabledUntil } returns 0 + every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC) val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 2200, end = 1000, daysEnabled = DayOfWeek.values().toSet()) val profiles = listOf(first.copy(schedule = schedule)) diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/Recipient_getChatColorsTest.kt b/app/src/test/java/org/thoughtcrime/securesms/recipients/Recipient_getChatColorsTest.kt index 898a46fdaf..09e7f33786 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/Recipient_getChatColorsTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/recipients/Recipient_getChatColorsTest.kt @@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.recipients import android.graphics.Color import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -26,19 +29,23 @@ class Recipient_getChatColorsTest : BaseRecipientTest() { @Before fun setUp() { - wallpaperValues = mockk() - chatColorsValues = mockk() + wallpaperValues = mockk() + chatColorsValues = mockk() val globalWallpaper = createWallpaper(globalWallpaperChatColor) every { wallpaperValues.wallpaper } answers { globalWallpaper } every { chatColorsValues.chatColors } answers { globalChatColor } - val mockStore = mockk() - SignalStore.testInject(mockStore) + mockkObject(SignalStore) every { SignalStore.wallpaper } returns wallpaperValues every { SignalStore.chatColors } returns chatColorsValues } + @After + fun tearDown() { + unmockkAll() + } + @Test fun `Given recipient has custom chat color set, when I getChatColors, then I expect the custom chat color`() { // GIVEN diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt index 84c98c3c7e..9556429fb4 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt @@ -2,35 +2,26 @@ package org.thoughtcrime.securesms.storage import android.app.Application import androidx.test.core.app.ApplicationProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.BeforeClass -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockedStatic -import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.internal.configuration.plugins.Plugins -import org.mockito.internal.junit.JUnitRule -import org.mockito.junit.MockitoRule -import org.mockito.quality.Strictness import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider -import org.thoughtcrime.securesms.keyvalue.AccountValues -import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet -import org.thoughtcrime.securesms.keyvalue.KeyValueStore -import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.testutil.EmptyLogger -import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.storage.SignalContactRecord @@ -42,24 +33,23 @@ import java.util.UUID @Config(application = Application::class) class ContactRecordProcessorTest { - @Rule - @JvmField - val mockitoRule: MockitoRule = JUnitRule(Plugins.getMockitoLogger(), Strictness.STRICT_STUBS) - - @Mock lateinit var recipientTable: RecipientTable - @Mock - lateinit var remoteConfig: MockedStatic - @Before fun setup() { - val mockAccountValues = mock(AccountValues::class.java) - Mockito.lenient().`when`(mockAccountValues.isPrimaryDevice).thenReturn(true) if (!AppDependencies.isInitialized) { AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) } - SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(KeyValueDataSet()))) + + mockkObject(SignalStore) + every { SignalStore.account.isPrimaryDevice } returns true + + recipientTable = mockk(relaxed = true) + } + + @After + fun tearDown() { + unmockkObject(SignalStore) } @Test diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.java b/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.java deleted file mode 100644 index 2177e8f290..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import android.app.Application; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.ParameterizedRobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.thoughtcrime.securesms.SignalStoreRule; -import org.thoughtcrime.securesms.dependencies.AppDependencies; -import org.thoughtcrime.securesms.keyvalue.AccountValues; - -import java.util.Arrays; -import java.util.Collection; - -import kotlin.Unit; - -import static junit.framework.TestCase.assertEquals; - -@RunWith(ParameterizedRobolectricTestRunner.class) -@Config(manifest = Config.NONE, application = Application.class) -public class SignalMeUtilText_parseE164FromLink { - - private final String input; - private final String output; - - @ParameterizedRobolectricTestRunner.Parameters - public static Collection data() { - return Arrays.asList(new Object[][]{ - { "https://signal.me/#p/+15555555555", "+15555555555" }, - { "https://signal.me/#p/5555555555", null }, - { "https://signal.me", null }, - { "https://signal.me/#p/", null }, - { "signal.me/#p/+15555555555", null }, - { "sgnl://signal.me/#p/+15555555555", "+15555555555" }, - { "sgnl://signal.me/#p/5555555555", null }, - { "sgnl://signal.me", null }, - { "sgnl://signal.me/#p/", null }, - { "", null }, - { null, null } - }); - } - - @Rule - public SignalStoreRule signalStore = new SignalStoreRule(dataSet -> { - dataSet.putString(AccountValues.KEY_E164, "+15555555555"); - return Unit.INSTANCE; - }); - - public SignalMeUtilText_parseE164FromLink(String input, String output) { - this.input = input; - this.output = output; - } - - @Test - public void parse() { - assertEquals(output, SignalMeUtil.parseE164FromLink(AppDependencies.getApplication(), input)); - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.kt b/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.kt new file mode 100644 index 0000000000..83af0b1b1b --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/util/SignalMeUtilText_parseE164FromLink.kt @@ -0,0 +1,64 @@ +package org.thoughtcrime.securesms.util + +import android.app.Application +import androidx.test.core.app.ApplicationProvider +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkAll +import junit.framework.TestCase +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.ParameterizedRobolectricTestRunner +import org.robolectric.annotation.Config +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.dependencies.AppDependencies.application +import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.SignalMeUtil.parseE164FromLink + +@RunWith(ParameterizedRobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class SignalMeUtilText_parseE164FromLink(private val input: String?, private val output: String?) { + + @Before + fun setUp() { + if (!AppDependencies.isInitialized) { + AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) + } + + mockkObject(SignalStore) + every { SignalStore.account.e164 } returns "+15555555555" + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun parse() { + TestCase.assertEquals(output, parseE164FromLink(application, input)) + } + + companion object { + @JvmStatic + @ParameterizedRobolectricTestRunner.Parameters + fun data(): Collection> { + return listOf( + arrayOf("https://signal.me/#p/+15555555555", "+15555555555"), + arrayOf("https://signal.me/#p/5555555555", null), + arrayOf("https://signal.me", null), + arrayOf("https://signal.me/#p/", null), + arrayOf("signal.me/#p/+15555555555", null), + arrayOf("sgnl://signal.me/#p/+15555555555", "+15555555555"), + arrayOf("sgnl://signal.me/#p/5555555555", null), + arrayOf("sgnl://signal.me", null), + arrayOf("sgnl://signal.me/#p/", null), + arrayOf("", null), + arrayOf(null, null) + ) + } + } +}