diff --git a/app/build.gradle b/app/build.gradle index 52632f7ea..a12a0565f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -541,6 +541,9 @@ dependencies { androidTestImplementation testLibs.androidx.test.ext.junit androidTestImplementation testLibs.espresso.core + androidTestImplementation testLibs.androidx.test.core + androidTestImplementation testLibs.androidx.test.core.ktx + androidTestImplementation testLibs.androidx.test.ext.junit.ktx testImplementation testLibs.espresso.core diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt new file mode 100644 index 000000000..061d0c5e9 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt @@ -0,0 +1,40 @@ +package org.thoughtcrime.securesms.conversation + +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog +import org.thoughtcrime.securesms.database.IdentityDatabase +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.testing.SignalActivityRule + +/** + * Android test to help show SNC dialog quickly with custom data to make sure it displays properly. + */ +@RunWith(AndroidJUnit4::class) +class SafetyNumberChangeDialogPreviewer { + + @get:Rule val harness = SignalActivityRule() + + @Test + fun testShowLongName() { + val other: Recipient = Recipient.resolved(harness.others.first()) + + SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Super really long name like omg", "But seriously it's long like really really long")) + + harness.setVerified(other, IdentityDatabase.VerifiedStatus.VERIFIED) + harness.changeIdentityKey(other) + + val scenario: ActivityScenario = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) } + scenario.onActivity { + SafetyNumberChangeDialog.show(it.supportFragmentManager, other.id) + } + + // Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately + // ThreadUtil.sleep(15000) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt new file mode 100644 index 000000000..d35cdaeea --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt @@ -0,0 +1,119 @@ +package org.thoughtcrime.securesms.testing + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.preference.PreferenceManager +import androidx.test.core.app.ActivityScenario +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.ExternalResource +import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.MasterSecretUtil +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.database.IdentityDatabase +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.registration.RegistrationData +import org.thoughtcrime.securesms.registration.RegistrationRepository +import org.thoughtcrime.securesms.registration.RegistrationUtil +import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.api.profiles.SignalServiceProfile +import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.internal.push.VerifyAccountResponse +import java.util.UUID + +/** + * Test rule to use that sets up the application in a mostly registered state. Enough so that most + * activities should be launchable directly. + * + * To use: `@get:Rule val harness = SignalActivityRule()` + */ +class SignalActivityRule : ExternalResource() { + + val application: Application = ApplicationDependencies.getApplication() + + lateinit var context: Context + private set + lateinit var self: Recipient + private set + lateinit var others: List + private set + + override fun before() { + context = InstrumentationRegistry.getInstrumentation().targetContext + self = setupSelf() + others = setupOthers() + } + + private fun setupSelf(): Recipient { + DeviceTransferBlockingInterceptor.getInstance().blockNetwork() + + PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit() + val masterSecret = MasterSecretUtil.generateMasterSecret(application, MasterSecretUtil.UNENCRYPTED_PASSPHRASE) + MasterSecretUtil.generateAsymmetricMasterSecret(application, masterSecret) + val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0) + preferences.edit().putBoolean("passphrase_initialized", true).commit() + + val registrationRepository = RegistrationRepository(application) + + registrationRepository.registerAccountWithoutRegistrationLock( + RegistrationData( + code = "123123", + e164 = "+15554045550101", + password = Util.getSecret(18), + registrationId = registrationRepository.registrationId, + profileKey = registrationRepository.getProfileKey("+15554045550101"), + fcmToken = null + ), + VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false) + ).blockingGet() + + SignalStore.kbsValues().optOut() + RegistrationUtil.maybeMarkRegistrationComplete(application) + SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) + + return Recipient.self() + } + + private fun setupOthers(): List { + val others = mutableListOf() + + for (i in 0..4) { + val aci = ACI.from(UUID.randomUUID()) + val recipientId = RecipientId.from(aci, "+1555555101$i") + SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i")) + SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) + SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true)) + SignalDatabase.recipients.setProfileSharing(recipientId, true) + ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey) + others += recipientId + } + + return others + } + + inline fun launchActivity(initIntent: Intent.() -> Unit): ActivityScenario { + return androidx.test.core.app.launchActivity(Intent(context, T::class.java).apply(initIntent)) + } + + fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) { + ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey) + } + + fun getIdentity(recipient: Recipient): IdentityKey { + return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0)) + } + + fun setVerified(recipient: Recipient, status: IdentityDatabase.VerifiedStatus) { + ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityDatabase.VerifiedStatus.VERIFIED) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java index ede5b7683..04606087c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java @@ -16,6 +16,8 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.database.model.IdentityRecord; +import org.thoughtcrime.securesms.util.ContextUtil; +import org.thoughtcrime.securesms.util.DrawableUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.adapter.AlwaysChangedDiffUtil; @@ -62,11 +64,9 @@ final class SafetyNumberChangeAdapter extends ListAdapter + + + + + @@ -777,6 +782,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java index e8c3836ac..ce71132be 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java @@ -206,6 +206,16 @@ public class SignalServiceProfile { @JsonCreator public Capabilities() {} + public Capabilities(boolean storage, boolean gv1Migration, boolean senderKey, boolean announcementGroup, boolean changeNumber, boolean stories, boolean giftBadges) { + this.storage = storage; + this.gv1Migration = gv1Migration; + this.senderKey = senderKey; + this.announcementGroup = announcementGroup; + this.changeNumber = changeNumber; + this.stories = stories; + this.giftBadges = giftBadges; + } + public boolean isStorage() { return storage; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java index b075d0e79..a2c77cf00 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java @@ -1,5 +1,6 @@ package org.whispersystems.signalservice.internal.push; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class VerifyAccountResponse { @@ -12,6 +13,15 @@ public class VerifyAccountResponse { @JsonProperty private boolean storageCapable; + @JsonCreator + public VerifyAccountResponse() {} + + public VerifyAccountResponse(String uuid, String pni, boolean storageCapable) { + this.uuid = uuid; + this.pni = pni; + this.storageCapable = storageCapable; + } + public String getUuid() { return uuid; }