From f010a3ec0dd284a3ee43edda01e3e96c7e3f87c8 Mon Sep 17 00:00:00 2001 From: Alan Evans <48254818+alan-signal@users.noreply.github.com> Date: Mon, 11 Mar 2019 16:40:26 -0300 Subject: [PATCH] Consistent Recipient to add contact Intent export. --- build.gradle | 10 ++- .../securesms/GroupMembersDialog.java | 11 +-- .../securesms/components/AvatarImageView.java | 13 +-- .../conversation/ConversationActivity.java | 10 +-- .../securesms/profiles/UnknownSenderView.java | 23 +---- .../recipients/RecipientExporter.java | 46 ++++++++++ .../recipients/RecipientExporterTest.java | 85 +++++++++++++++++++ 7 files changed, 148 insertions(+), 50 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/recipients/RecipientExporter.java create mode 100644 test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java diff --git a/build.gradle b/build.gradle index f5a650e84..d6f355c02 100644 --- a/build.gradle +++ b/build.gradle @@ -142,7 +142,7 @@ dependencies { } testImplementation 'junit:junit:4.12' - testImplementation 'org.assertj:assertj-core:1.7.1' + testImplementation 'org.assertj:assertj-core:3.11.1' testImplementation 'org.mockito:mockito-core:1.9.5' testImplementation 'org.powermock:powermock-api-mockito:1.6.1' testImplementation 'org.powermock:powermock-module-junit4:1.6.1' @@ -160,6 +160,8 @@ dependencies { exclude group: 'org.hamcrest', module: 'hamcrest-core' exclude group: 'com.android.support', module: 'support-annotations' } + testImplementation 'org.robolectric:robolectric:4.2' + testImplementation 'org.robolectric:shadows-multidex:4.2' } dependencyVerification { @@ -422,6 +424,12 @@ android { lintOptions { abortOnError false } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } def assembleWebsiteDescriptor = { variant, file -> diff --git a/src/org/thoughtcrime/securesms/GroupMembersDialog.java b/src/org/thoughtcrime/securesms/GroupMembersDialog.java index 8cb209d62..997f256de 100644 --- a/src/org/thoughtcrime/securesms/GroupMembersDialog.java +++ b/src/org/thoughtcrime/securesms/GroupMembersDialog.java @@ -4,12 +4,12 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; -import android.provider.ContactsContract; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientExporter; import org.thoughtcrime.securesms.util.Util; import java.util.LinkedList; @@ -70,14 +70,7 @@ public class GroupMembersDialog extends AsyncTask> { context.startActivity(intent); } else { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - if (recipient.getAddress().isEmail()) { - intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString()); - } else { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString()); - } - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - context.startActivity(intent); + context.startActivity(RecipientExporter.export(recipient).asAddContactIntent()); } } } diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java index 7ea9bffbf..d781095a3 100644 --- a/src/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.components; import android.content.Context; -import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; @@ -11,16 +10,15 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; -import android.view.View; import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.avatars.ContactColors; -import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientExporter; import org.thoughtcrime.securesms.util.ThemeUtil; public class AvatarImageView extends AppCompatImageView { @@ -110,14 +108,7 @@ public class AvatarImageView extends AppCompatImageView { if (recipient.getContactUri() != null) { ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); } else { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - if (recipient.getAddress().isEmail()) { - intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString()); - } else { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString()); - } - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - getContext().startActivity(intent); + getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent()); } }); } else { diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 43f7901bd..7edbca258 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -181,6 +181,7 @@ import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; +import org.thoughtcrime.securesms.recipients.RecipientExporter; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; import org.thoughtcrime.securesms.search.model.MessageResult; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -1110,14 +1111,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (recipient.getAddress().isGroup()) return; try { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - if (recipient.getAddress().isEmail()) { - intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString()); - } else { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString()); - } - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - startActivityForResult(intent, ADD_CONTACT); + startActivityForResult(RecipientExporter.export(recipient).asAddContactIntent(), ADD_CONTACT); } catch (ActivityNotFoundException e) { Log.w(TAG, e); } diff --git a/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java index a9c72b7ab..55aa99aae 100644 --- a/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java +++ b/src/org/thoughtcrime/securesms/profiles/UnknownSenderView.java @@ -1,20 +1,16 @@ package org.thoughtcrime.securesms.profiles; - import android.content.Context; -import android.content.Intent; import android.os.AsyncTask; -import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; -import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientExporter; import org.thoughtcrime.securesms.util.ViewUtil; public class UnknownSenderView extends FrameLayout { @@ -61,22 +57,7 @@ public class UnknownSenderView extends FrameLayout { } private void handleAdd() { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - - if (!TextUtils.isEmpty(recipient.getProfileName())) { - intent.putExtra(ContactsContract.Intents.Insert.NAME, recipient.getProfileName()); - } - - if (recipient.getAddress().isEmail()) { - intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString()); - } - - if (recipient.getAddress().isPhone()) { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString()); - } - - getContext().startActivity(intent); + getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent()); if (threadId != -1) DatabaseFactory.getThreadDatabase(getContext()).setHasSent(threadId, true); } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java new file mode 100644 index 000000000..d8caceedf --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/RecipientExporter.java @@ -0,0 +1,46 @@ +package org.thoughtcrime.securesms.recipients; + +import android.content.Intent; +import android.provider.ContactsContract; +import android.text.TextUtils; + +import org.thoughtcrime.securesms.database.Address; + +import static android.content.Intent.ACTION_INSERT_OR_EDIT; + +public final class RecipientExporter { + + public static RecipientExporter export(Recipient recipient) { + return new RecipientExporter(recipient); + } + + private final Recipient recipient; + + private RecipientExporter(Recipient recipient) { + this.recipient = recipient; + } + + public Intent asAddContactIntent() { + Intent intent = new Intent(ACTION_INSERT_OR_EDIT); + intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); + addNameToIntent(intent, recipient.getProfileName()); + addAddressToIntent(intent, recipient.getAddress()); + return intent; + } + + private static void addNameToIntent(Intent intent, String profileName) { + if (!TextUtils.isEmpty(profileName)) { + intent.putExtra(ContactsContract.Intents.Insert.NAME, profileName); + } + } + + private static void addAddressToIntent(Intent intent, Address address) { + if (address.isPhone()) { + intent.putExtra(ContactsContract.Intents.Insert.PHONE, address.toPhoneString()); + } else if (address.isEmail()) { + intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address.toEmailString()); + } else { + throw new RuntimeException("Cannot export Recipient with neither phone nor email"); + } + } +} diff --git a/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java b/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java new file mode 100644 index 000000000..54a26722a --- /dev/null +++ b/test/unitTest/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java @@ -0,0 +1,85 @@ +package org.thoughtcrime.securesms.recipients; + +import android.app.Application; +import android.content.Intent; +import android.provider.ContactsContract; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.thoughtcrime.securesms.database.Address; + +import static org.assertj.core.api.Assertions.*; + +import static android.provider.ContactsContract.Intents.Insert.NAME; +import static android.provider.ContactsContract.Intents.Insert.EMAIL; +import static android.provider.ContactsContract.Intents.Insert.PHONE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, application = Application.class) +public final class RecipientExporterTest { + + @Test + public void asAddContactIntent_with_phone_number() { + Recipient recipient = givenRecipient("Alice", givenPhoneNumber("+1555123456")); + + Intent intent = RecipientExporter.export(recipient).asAddContactIntent(); + + assertEquals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction()); + assertEquals(ContactsContract.Contacts.CONTENT_ITEM_TYPE, intent.getType()); + assertEquals("Alice", intent.getStringExtra(NAME)); + assertEquals("+1555123456", intent.getStringExtra(PHONE)); + assertNull(intent.getStringExtra(EMAIL)); + } + + @Test + public void asAddContactIntent_with_email() { + Recipient recipient = givenRecipient("Bob", givenEmail("bob@signal.org")); + + Intent intent = RecipientExporter.export(recipient).asAddContactIntent(); + + assertEquals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction()); + assertEquals(ContactsContract.Contacts.CONTENT_ITEM_TYPE, intent.getType()); + assertEquals("Bob", intent.getStringExtra(NAME)); + assertEquals("bob@signal.org", intent.getStringExtra(EMAIL)); + assertNull(intent.getStringExtra(PHONE)); + } + + @Test + public void asAddContactIntent_with_neither_email_nor_phone() { + RecipientExporter exporter = RecipientExporter.export(givenRecipient("Bob", mock(Address.class))); + + assertThatThrownBy(exporter::asAddContactIntent).isExactlyInstanceOf(RuntimeException.class) + .hasMessage("Cannot export Recipient with neither phone nor email"); + } + + private Recipient givenRecipient(String profileName, Address address) { + Recipient recipient = mock(Recipient.class); + when(recipient.getProfileName()).thenReturn(profileName); + when(recipient.getAddress()).thenReturn(address); + return recipient; + } + + private Address givenPhoneNumber(String phoneNumber) { + Address address = mock(Address.class); + when(address.isPhone()).thenReturn(true); + when(address.toPhoneString()).thenReturn(phoneNumber); + when(address.toEmailString()).thenThrow(new RuntimeException()); + return address; + } + + private Address givenEmail(String email) { + Address address = mock(Address.class); + when(address.isEmail()).thenReturn(true); + when(address.toEmailString()).thenReturn(email); + when(address.toPhoneString()).thenThrow(new RuntimeException()); + return address; + } +}