Commit 2eac1442 authored by Finnur Thorarinsson's avatar Finnur Thorarinsson Committed by Commit Bot

Contacts Picker: Make contact details of owner more prominent.

Bring the contact, who is currently signed in, to the top.
If no user is signed in, we can still synthesize a contact
entry with the available information (best-effort).

Note: This CL uses a blue star to mark the record that was
moved/synthesized. This should be considered a placeholder.
Will work with UX to flesh out how it should look like.

BUG: 860467

Change-Id: I9c8a7509feac5bc38e8e60878cef9db8acb420e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1876348
Commit-Queue: Finnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Auto-Submit: Finnur Thorarinsson <finnur@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712574}
parent 438aaaa4
...@@ -92,6 +92,7 @@ chrome_test_java_sources = [ ...@@ -92,6 +92,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java", "javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java",
"javatests/src/org/chromium/chrome/browser/directactions/FakeDirectActionReporter.java", "javatests/src/org/chromium/chrome/browser/directactions/FakeDirectActionReporter.java",
"javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java", "javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java",
"javatests/src/org/chromium/chrome/browser/contacts_picker/ContactDetailsTest.java",
"javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java", "javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java",
"javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java", "javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java",
"javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java", "javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java",
......
...@@ -74,4 +74,11 @@ ...@@ -74,4 +74,11 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/star"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="20dp"
android:src="@drawable/btn_star_filled"
android:visibility="gone" />
</merge> </merge>
...@@ -509,6 +509,9 @@ ...@@ -509,6 +509,9 @@
<dimen name="circular_monogram_size">20dp</dimen> <dimen name="circular_monogram_size">20dp</dimen>
<dimen name="circular_monogram_text_size">14dp</dimen> <dimen name="circular_monogram_text_size">14dp</dimen>
<!-- Contact Picker dimensions -->
<dimen name="contact_picker_icon_size">36dp</dimen>
<!-- Photo Picker dimensions --> <!-- Photo Picker dimensions -->
<dimen name="photo_picker_selected_padding">12dp</dimen> <dimen name="photo_picker_selected_padding">12dp</dimen>
<dimen name="photo_picker_label_gap">10dp</dimen> <dimen name="photo_picker_label_gap">10dp</dimen>
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.contacts_picker; package org.chromium.chrome.browser.contacts_picker;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -19,6 +20,10 @@ import java.util.List; ...@@ -19,6 +20,10 @@ import java.util.List;
* A class to keep track of the metadata associated with a contact. * A class to keep track of the metadata associated with a contact.
*/ */
public class ContactDetails implements Comparable<ContactDetails> { public class ContactDetails implements Comparable<ContactDetails> {
// The identifier for the information from the signed in user. Must not be a valid id in the
// context of the Android Contacts list.
public static final String SELF_CONTACT_ID = "-1";
/** /**
* A container class for delivering contact details in abbreviated form * A container class for delivering contact details in abbreviated form
* (where only the first email and phone numbers are returned and the rest * (where only the first email and phone numbers are returned and the rest
...@@ -46,6 +51,14 @@ public class ContactDetails implements Comparable<ContactDetails> { ...@@ -46,6 +51,14 @@ public class ContactDetails implements Comparable<ContactDetails> {
// The list of addresses registered for this contact. // The list of addresses registered for this contact.
private final List<PaymentAddress> mAddresses; private final List<PaymentAddress> mAddresses;
// Keeps track of whether this is the contact detail for the owner of the device.
private boolean mIsSelf;
// The avatar icon for the owner of the device. Non-null only if the ContactDetails representing
// the owner were synthesized (not when a pre-existing contact tile was moved to the top).
@Nullable
private Drawable mSelfIcon;
/** /**
* The ContactDetails constructor. * The ContactDetails constructor.
* @param id The unique identifier of this contact. * @param id The unique identifier of this contact.
...@@ -88,6 +101,37 @@ public class ContactDetails implements Comparable<ContactDetails> { ...@@ -88,6 +101,37 @@ public class ContactDetails implements Comparable<ContactDetails> {
return mId; return mId;
} }
/**
* Marks whether object is representing the owner of the device.
* @param value True if this is the contact details for the owner. False otherwise.
*/
public void setIsSelf(boolean value) {
mIsSelf = value;
}
/**
* Returns true if this contact detail is representing the owner of the device.
*/
public boolean isSelf() {
return mIsSelf;
}
/**
* Sets the icon representing the owner of the device.
*/
public void setSelfIcon(Drawable icon) {
mSelfIcon = icon;
}
/**
* Fetch the cached icon for this contact. Returns null if this is not the 'self' contact, all
* other contact avatars should be retrieved through the {@link FetchIconWorkerTask}.
*/
@Nullable
public Drawable getSelfIcon() {
return mSelfIcon;
}
/** /**
* Accessor for the abbreviated display name (first letter of first name and first letter of * Accessor for the abbreviated display name (first letter of first name and first letter of
* last name). * last name).
......
...@@ -13,6 +13,7 @@ import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; ...@@ -13,6 +13,7 @@ import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
...@@ -49,6 +50,9 @@ public class ContactView extends SelectableItemView<ContactDetails> { ...@@ -49,6 +50,9 @@ public class ContactView extends SelectableItemView<ContactDetails> {
private TextView mPhoneNumber; private TextView mPhoneNumber;
private TextView mPhoneNumberOverflowCount; private TextView mPhoneNumberOverflowCount;
// The UI that indicates this is the owner of the device.
private ImageView mStar;
// The dialog manager to use to show contact details. // The dialog manager to use to show contact details.
private ModalDialogManager mManager; private ModalDialogManager mManager;
...@@ -74,6 +78,7 @@ public class ContactView extends SelectableItemView<ContactDetails> { ...@@ -74,6 +78,7 @@ public class ContactView extends SelectableItemView<ContactDetails> {
mEmailOverflowCount = findViewById(R.id.email_overflow_count); mEmailOverflowCount = findViewById(R.id.email_overflow_count);
mPhoneNumber = findViewById(R.id.telephone_number); mPhoneNumber = findViewById(R.id.telephone_number);
mPhoneNumberOverflowCount = findViewById(R.id.telephone_number_overflow_count); mPhoneNumberOverflowCount = findViewById(R.id.telephone_number_overflow_count);
mStar = findViewById(R.id.star);
mEmailOverflowCount.setOnClickListener(this); mEmailOverflowCount.setOnClickListener(this);
mPhoneNumberOverflowCount.setOnClickListener(this); mPhoneNumberOverflowCount.setOnClickListener(this);
...@@ -181,6 +186,8 @@ public class ContactView extends SelectableItemView<ContactDetails> { ...@@ -181,6 +186,8 @@ public class ContactView extends SelectableItemView<ContactDetails> {
updateTextViewVisibilityAndContent( updateTextViewVisibilityAndContent(
mPhoneNumberOverflowCount, details.overflowTelephoneNumberCount); mPhoneNumberOverflowCount, details.overflowTelephoneNumberCount);
if (contactDetails.isSelf()) mStar.setVisibility(View.VISIBLE);
if (icon == null) { if (icon == null) {
icon = mCategoryView.getIconGenerator().generateIconForText( icon = mCategoryView.getIconGenerator().generateIconForText(
contactDetails.getDisplayNameAbbreviation()); contactDetails.getDisplayNameAbbreviation());
...@@ -212,5 +219,6 @@ public class ContactView extends SelectableItemView<ContactDetails> { ...@@ -212,5 +219,6 @@ public class ContactView extends SelectableItemView<ContactDetails> {
mEmailOverflowCount.setText(""); mEmailOverflowCount.setText("");
mPhoneNumber.setText(""); mPhoneNumber.setText("");
mPhoneNumberOverflowCount.setText(""); mPhoneNumberOverflowCount.setText("");
mStar.setVisibility(View.GONE);
} }
} }
...@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.contacts_picker; ...@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.contacts_picker;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -61,12 +63,19 @@ public class ContactViewHolder ...@@ -61,12 +63,19 @@ public class ContactViewHolder
return; return;
} }
Bitmap icon = mCategoryView.getIconCache().getBitmap(mContact.getId()); Drawable drawable = contact.getSelfIcon();
if (icon == null) { if (drawable != null) {
mWorkerTask = new FetchIconWorkerTask(mContact.getId(), mContentResolver, this); assert drawable instanceof BitmapDrawable;
mWorkerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
mItemView.initialize(contact, bitmap);
} else {
Bitmap icon = mCategoryView.getIconCache().getBitmap(mContact.getId());
if (icon == null) {
mWorkerTask = new FetchIconWorkerTask(mContact.getId(), mContentResolver, this);
mWorkerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
mItemView.initialize(contact, icon);
} }
mItemView.initialize(contact, icon);
} }
/** /**
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.contacts_picker; package org.chromium.chrome.browser.contacts_picker;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract; import android.provider.ContactsContract;
...@@ -38,6 +39,9 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> { ...@@ -38,6 +39,9 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
void contactsRetrieved(ArrayList<ContactDetails> contacts); void contactsRetrieved(ArrayList<ContactDetails> contacts);
} }
// The current context to use.
private Context mContext;
// The content resolver to use for looking up contacts. // The content resolver to use for looking up contacts.
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
...@@ -58,17 +62,18 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> { ...@@ -58,17 +62,18 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
/** /**
* A ContactsFetcherWorkerTask constructor. * A ContactsFetcherWorkerTask constructor.
* @param contentResolver The ContentResolver to use to fetch the contacts data. * @param context The Context to use.
* @param callback The callback to use to communicate back the results. * @param callback The callback to use to communicate back the results.
* @param includeNames Whether names were requested by the website. * @param includeNames Whether names were requested by the website.
* @param includeEmails Whether to include emails in the data fetched. * @param includeEmails Whether to include emails in the data fetched.
* @param includeTel Whether to include telephones in the data fetched. * @param includeTel Whether to include telephones in the data fetched.
* @param includeAddresses Whether to include telephones in the data fetched. * @param includeAddresses Whether to include telephones in the data fetched.
*/ */
public ContactsFetcherWorkerTask(ContentResolver contentResolver, public ContactsFetcherWorkerTask(Context context, ContactsRetrievedCallback callback,
ContactsRetrievedCallback callback, boolean includeNames, boolean includeEmails, boolean includeNames, boolean includeEmails, boolean includeTel,
boolean includeTel, boolean includeAddresses) { boolean includeAddresses) {
mContentResolver = contentResolver; mContext = context;
mContentResolver = context.getContentResolver();
mCallback = callback; mCallback = callback;
mIncludeNames = includeNames; mIncludeNames = includeNames;
mIncludeEmails = includeEmails; mIncludeEmails = includeEmails;
......
...@@ -51,6 +51,8 @@ class FetchIconWorkerTask extends AsyncTask<Bitmap> { ...@@ -51,6 +51,8 @@ class FetchIconWorkerTask extends AsyncTask<Bitmap> {
public FetchIconWorkerTask( public FetchIconWorkerTask(
String id, ContentResolver contentResolver, IconRetrievedCallback callback) { String id, ContentResolver contentResolver, IconRetrievedCallback callback) {
mContactId = id; mContactId = id;
// Avatar icon for own info should not be obtained through the contacts list.
assert !id.equals(ContactDetails.SELF_CONTACT_ID);
mContentResolver = contentResolver; mContentResolver = contentResolver;
mCallback = callback; mCallback = callback;
} }
......
...@@ -151,7 +151,7 @@ public class PickerCategoryView extends OptimizedFrameLayout ...@@ -151,7 +151,7 @@ public class PickerCategoryView extends OptimizedFrameLayout
R.string.contacts_picker_no_contacts_found, R.string.contacts_picker_no_contacts_found,
R.string.contacts_picker_no_contacts_found); R.string.contacts_picker_no_contacts_found);
mPickerAdapter = new PickerAdapter(this, context.getContentResolver(), formattedOrigin); mPickerAdapter = new PickerAdapter(this, context, formattedOrigin);
mRecyclerView = mSelectableListLayout.initializeRecyclerView(mPickerAdapter); mRecyclerView = mSelectableListLayout.initializeRecyclerView(mPickerAdapter);
int titleId = multiSelectionAllowed ? R.string.contacts_picker_select_contacts int titleId = multiSelectionAllowed ? R.string.contacts_picker_select_contacts
: R.string.contacts_picker_select_contact; : R.string.contacts_picker_select_contact;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contacts_picker;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.payments.mojom.PaymentAddress;
import java.util.Arrays;
import java.util.List;
/**
* Tests for the ContactDetails class.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
public class ContactDetailsTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
Context mContext;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getApplicationContext();
}
private void compareAbbreviatedContactDetails(ContactDetails.AbbreviatedContactDetails expected,
ContactDetails.AbbreviatedContactDetails actual) {
Assert.assertEquals(expected.primaryEmail, actual.primaryEmail);
Assert.assertEquals(expected.overflowEmailCount, actual.overflowEmailCount);
Assert.assertEquals(expected.primaryTelephoneNumber, actual.primaryTelephoneNumber);
Assert.assertEquals(
expected.overflowTelephoneNumberCount, actual.overflowTelephoneNumberCount);
}
@Test
@SmallTest
public void testBasics() {
PaymentAddress address1 = new PaymentAddress();
address1.city = "city";
address1.country = "country";
address1.addressLine = new String[] {"formattedAddress1"};
address1.postalCode = "postalCode";
address1.region = "region";
address1.dependentLocality = "";
address1.sortingCode = "";
address1.organization = "";
address1.recipient = "";
address1.phone = "";
PaymentAddress address2 = new PaymentAddress();
address2.city = "city";
address2.country = "country";
address2.addressLine = new String[] {"formattedAddress2"};
address2.postalCode = "postalCode";
address2.region = "region";
address2.dependentLocality = "";
address2.sortingCode = "";
address2.organization = "";
address2.recipient = "";
address2.phone = "";
ContactDetails contact = new ContactDetails("id", "Display Name",
Arrays.asList("email@example.com", "email2@example.com"),
Arrays.asList("555 123-4567", "555 765-4321"), Arrays.asList(address1, address2));
Assert.assertEquals("id", contact.getId());
Assert.assertEquals("Display Name", contact.getDisplayName());
Assert.assertEquals("DN", contact.getDisplayNameAbbreviation());
List<String> emails = contact.getEmails();
Assert.assertEquals(2, emails.size());
Assert.assertEquals("email@example.com", emails.get(0));
Assert.assertEquals("email2@example.com", emails.get(1));
List<String> telephones = contact.getPhoneNumbers();
Assert.assertEquals(2, telephones.size());
Assert.assertEquals("555 123-4567", telephones.get(0));
Assert.assertEquals("555 765-4321", telephones.get(1));
List<PaymentAddress> addresses = contact.getAddresses();
Assert.assertEquals(2, addresses.size());
Assert.assertEquals("formattedAddress1", addresses.get(0).addressLine[0]);
Assert.assertEquals("formattedAddress2", addresses.get(1).addressLine[0]);
Assert.assertEquals(false, contact.isSelf());
Assert.assertEquals(null, contact.getSelfIcon());
contact.setIsSelf(true);
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.BLUE);
BitmapDrawable drawable = new BitmapDrawable(bitmap);
contact.setSelfIcon(drawable);
Assert.assertEquals(true, contact.isSelf());
Assert.assertTrue(null != contact.getSelfIcon());
Assert.assertEquals("", contact.getContactDetailsAsString(false, false));
Assert.assertEquals("email@example.com\nemail2@example.com",
contact.getContactDetailsAsString(true, false));
Assert.assertEquals(
"555 123-4567\n555 765-4321", contact.getContactDetailsAsString(false, true));
Resources resources = mContext.getResources();
ContactDetails.AbbreviatedContactDetails expected =
new ContactDetails.AbbreviatedContactDetails();
expected.primaryEmail = "email@example.com";
expected.overflowEmailCount = "(+ 1 more)";
expected.primaryTelephoneNumber = "555 123-4567";
expected.overflowTelephoneNumberCount = "(+ 1 more)";
// Test with full details.
ContactDetails.AbbreviatedContactDetails actual = contact.getAbbreviatedContactDetails(
/*includeEmails=*/true, /*includeTels=*/true, resources);
compareAbbreviatedContactDetails(expected, actual);
// Test with only email details.
actual = contact.getAbbreviatedContactDetails(
/*includeEmails=*/true, /*includeTels=*/false, resources);
expected.primaryTelephoneNumber = "";
expected.overflowTelephoneNumberCount = "";
compareAbbreviatedContactDetails(expected, actual);
// Test with no details.
actual = contact.getAbbreviatedContactDetails(
/*includeEmails=*/false, /*includeTels=*/false, resources);
expected.primaryEmail = "";
expected.overflowEmailCount = "";
compareAbbreviatedContactDetails(expected, actual);
// Test with only telephone details.
actual = contact.getAbbreviatedContactDetails(
/*includeEmails=*/false, /*includeTels=*/true, resources);
expected.primaryTelephoneNumber = "555 123-4567";
expected.overflowTelephoneNumberCount = "(+ 1 more)";
compareAbbreviatedContactDetails(expected, actual);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment