Update the way of drawing letter tile
Added config to determine how many letters to show for contacts
that do not have avatars.
Bug: 142914836
Test: Manually
Change-Id: I16571467d65f210afabf9293fc408132ee4dd40e
diff --git a/car-apps-common/res/values/config.xml b/car-apps-common/res/values/config.xml
index 1b8514d..f274eb8 100644
--- a/car-apps-common/res/values/config.xml
+++ b/car-apps-common/res/values/config.xml
@@ -35,4 +35,10 @@
<string name="config_letter_tile_font_family" translatable="false">sans-serif-light</string>
<!-- Typeface.NORMAL=0; Typeface.BOLD=1; Typeface.ITALIC=2; Typeface.BOLD_ITALIC=3-->
<integer name="config_letter_tile_text_style">0</integer>
+
+ <!-- This value will determine how many letters to show in a letter tile drawable for
+ the contacts that don't have avatars. The value can be 2 (show initials),
+ 1 (show one letter) or 0 (show avatar anonymous icon)
+ -->
+ <integer name="config_number_of_letters_shown_for_avatar">1</integer>
</resources>
diff --git a/car-apps-common/src/com/android/car/apps/common/LetterTileDrawable.java b/car-apps-common/src/com/android/car/apps/common/LetterTileDrawable.java
index 5abeefe..3a698b0 100644
--- a/car-apps-common/src/com/android/car/apps/common/LetterTileDrawable.java
+++ b/car-apps-common/src/com/android/car/apps/common/LetterTileDrawable.java
@@ -15,7 +15,6 @@
*/
package com.android.car.apps.common;
-import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -29,6 +28,8 @@
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
+
/**
* A drawable that encapsulates all the functionality needed to display a letter tile to
* represent a contact image.
@@ -46,7 +47,6 @@
/** Reusable components to avoid new allocations */
private static final Paint sPaint = new Paint();
private static final Rect sRect = new Rect();
- private static final char[] sFirstChar = new char[1];
/** Contact type constants */
public static final int TYPE_PERSON = 1;
@@ -56,30 +56,43 @@
private final Paint mPaint;
- @Nullable private String mDisplayName;
+ private String mLetters;
private int mColor;
private int mContactType = TYPE_DEFAULT;
private float mScale = 1.0f;
private float mOffset = 0.0f;
private boolean mIsCircle = false;
- // TODO(rogerxue): the use pattern for this class is always:
- // create LTD, setContactDetails(), setIsCircular(true). merge them into ctor.
+ /**
+ * A custom Drawable that draws letters on a colored background.
+ */
+ // The use pattern for this constructor is:
+ // create LTD, setContactDetails(), and setIsCircular(true) if needed.
public LetterTileDrawable(final Resources res) {
+ this(res, null, null);
+ }
+
+ /**
+ * A custom Drawable that draws letters on a colored background.
+ */
+ // This constructor allows passing the letters and identifier directly. There is no need to
+ // call setContactDetails() again. setIsCircular(true) needs to be called separately if needed.
+ public LetterTileDrawable(final Resources res, @Nullable String letters,
+ @Nullable String identifier) {
mPaint = new Paint();
mPaint.setFilterBitmap(true);
mPaint.setDither(true);
setScale(0.7f);
if (sColors == null) {
- sDefaultColor = res.getColor(R.color.letter_tile_default_color);
+ sDefaultColor = res.getColor(R.color.letter_tile_default_color, null /* theme */);
TypedArray ta = res.obtainTypedArray(R.array.letter_tile_colors);
if (ta.length() == 0) {
// TODO(dnotario). Looks like robolectric shadow doesn't currently support
// obtainTypedArray and always returns length 0 array, which will make some code
// below that does a division by length of sColors choke. Workaround by creating
// an array of length 1. A more proper fix tracked by b/26518438.
- sColors = new int[] { sDefaultColor };
+ sColors = new int[]{sDefaultColor};
} else {
sColors = new int[ta.length()];
@@ -89,7 +102,7 @@
ta.recycle();
}
- sTileFontColor = res.getColor(R.color.letter_tile_font_color);
+ sTileFontColor = res.getColor(R.color.letter_tile_font_color, null /* theme */);
sLetterToTileRatio = res.getFraction(R.fraction.letter_to_tile_ratio, 1, 1);
// TODO: get images for business and voicemail
sDefaultPersonAvatar = res.getDrawable(R.drawable.ic_person, null /* theme */);
@@ -101,6 +114,8 @@
sPaint.setTextAlign(Align.CENTER);
sPaint.setAntiAlias(true);
}
+
+ setContactDetails(letters, identifier);
}
@Override
@@ -150,20 +165,16 @@
canvas.drawRect(bounds, sPaint);
}
- // Draw letter/digit only if the first character is an english letter
- if (!TextUtils.isEmpty(mDisplayName) && isEnglishLetter(mDisplayName.charAt(0))) {
- // Draw letter or digit.
- sFirstChar[0] = Character.toUpperCase(mDisplayName.charAt(0));
-
+ if (!TextUtils.isEmpty(mLetters)) {
// Scale text by canvas bounds and user selected scaling factor
sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
//sPaint.setTextSize(sTileLetterFontSize);
- sPaint.getTextBounds(sFirstChar, 0, 1, sRect);
+ sPaint.getTextBounds(mLetters, 0, mLetters.length(), sRect);
sPaint.setColor(sTileFontColor);
// Draw the letter in the canvas, vertically shifted up or down by the user-defined
// offset
- canvas.drawText(sFirstChar, 0, 1, bounds.centerX(),
+ canvas.drawText(mLetters, 0, mLetters.length(), bounds.centerX(),
bounds.centerY() + mOffset * bounds.height() + sRect.height() / 2,
sPaint);
} else {
@@ -204,10 +215,6 @@
}
}
- private static boolean isEnglishLetter(final char c) {
- return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
- }
-
@Override
public void setAlpha(final int alpha) {
mPaint.setAlpha(alpha);
@@ -227,7 +234,7 @@
* Scale the drawn letter tile to a ratio of its default size
*
* @param scale The ratio the letter tile should be scaled to as a percentage of its default
- * size, from a scale of 0 to 2.0f. The default is 1.0f.
+ * size, from a scale of 0 to 2.0f. The default is 1.0f.
*/
public void setScale(float scale) {
mScale = scale;
@@ -237,20 +244,26 @@
* Assigns the vertical offset of the position of the letter tile to the ContactDrawable
*
* @param offset The provided offset must be within the range of -0.5f to 0.5f.
- * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
- * it is being drawn on, which means it will be drawn with the center of the letter starting
- * at the top edge of the canvas.
- * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas
- * it is being drawn on, which means it will be drawn with the center of the letter starting
- * at the bottom edge of the canvas.
- * The default is 0.0f.
+ * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of
+ * the canvas it is being drawn on, which means it will be drawn with the center
+ * of the letter starting at the top edge of the canvas.
+ * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of
+ * the canvas it is being drawn on, which means it will be drawn with the center
+ * of the letter starting at the bottom edge of the canvas.
+ * The default is 0.0f.
*/
public void setOffset(float offset) {
mOffset = offset;
}
- public void setContactDetails(@Nullable String displayName, String identifier) {
- mDisplayName = displayName;
+ /**
+ * Sets the details.
+ *
+ * @param letters The letters need to be drawn
+ * @param identifier decides the color for the drawable.
+ */
+ public void setContactDetails(@Nullable String letters, @Nullable String identifier) {
+ mLetters = letters;
mColor = pickColor(identifier);
}
@@ -264,6 +277,7 @@
/**
* Convert the drawable to a bitmap.
+ *
* @param size The target size of the bitmap.
* @return A bitmap representation of the drawable.
*/
diff --git a/car-telephony-common/src/com/android/car/telephony/common/Contact.java b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
index 90ae0f7..69904b4 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/Contact.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
@@ -161,6 +161,11 @@
private PhoneNumber mPrimaryPhoneNumber;
/**
+ * The initials of the contact's name.
+ */
+ private String mInitials;
+
+ /**
* Parses a Contact entry for a Cursor loaded from the Contact Database.
*/
public static Contact fromCursor(Context context, Cursor cursor) {
@@ -297,6 +302,19 @@
}
/**
+ * Returns the initials of the contact's name.
+ */
+ //TODO: update how to get initials after refactoring. Could use last name and first name to
+ // get initials after refactoring to avoid error for those names with prefix.
+ public String getInitials() {
+ if (mInitials == null) {
+ mInitials = TelecomUtils.getInitials(mDisplayName, mAltDisplayName);
+ }
+
+ return mInitials;
+ }
+
+ /**
* Merges a Contact entry with another if they represent different numbers of the same contact.
*
* @return A merged contact.
diff --git a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
index f3a2b0e..25ac9d4 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
@@ -147,13 +147,15 @@
public static final class PhoneNumberInfo {
private final String mPhoneNumber;
private final String mDisplayName;
+ private final String mInitials;
private final Uri mAvatarUri;
private final String mTypeLabel;
public PhoneNumberInfo(String phoneNumber, String displayName,
- Uri avatarUri, String typeLabel) {
+ String initials, Uri avatarUri, String typeLabel) {
mPhoneNumber = phoneNumber;
mDisplayName = displayName;
+ mInitials = initials;
mAvatarUri = avatarUri;
mTypeLabel = typeLabel;
}
@@ -166,6 +168,16 @@
return mDisplayName;
}
+ /**
+ * Returns the initials of the contact related to the phone number. Returns null if there is
+ * no related contact.
+ */
+ @Nullable
+ public String getInitials() {
+ return mInitials;
+ }
+
+ @Nullable
public Uri getAvatarUri() {
return mAvatarUri;
}
@@ -189,6 +201,7 @@
number,
context.getString(R.string.unknown),
null,
+ null,
""));
}
@@ -196,6 +209,7 @@
return CompletableFuture.completedFuture(new PhoneNumberInfo(
number,
context.getString(R.string.voicemail),
+ null,
makeResourceUri(context, R.drawable.ic_voicemail),
""));
}
@@ -223,6 +237,7 @@
return CompletableFuture.completedFuture(new PhoneNumberInfo(
number,
name,
+ contact.getInitials(),
contact.getAvatarUri(),
typeLabel.toString()));
}
@@ -230,13 +245,16 @@
return CompletableFuture.supplyAsync(() -> {
String name = null;
+ String nameAlt = null;
String photoUriString = null;
CharSequence typeLabel = "";
ContentResolver cr = context.getContentResolver();
+ String initials;
try (Cursor cursor = cr.query(
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
- new String[] {
+ new String[]{
PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.DISPLAY_NAME_ALTERNATIVE,
PhoneLookup.PHOTO_URI,
PhoneLookup.TYPE,
PhoneLookup.LABEL,
@@ -244,14 +262,23 @@
null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
- name = cursor.getString(0);
- photoUriString = cursor.getString(1);
- int type = cursor.getInt(2);
- String label = cursor.getString(3);
+ int nameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
+ int altNameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME_ALTERNATIVE);
+ int photoUriColumn = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+ int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
+ int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
+
+ name = cursor.getString(nameColumn);
+ nameAlt = cursor.getString(altNameColumn);
+ photoUriString = cursor.getString(photoUriColumn);
+ int type = cursor.getInt(typeColumn);
+ String label = cursor.getString(labelColumn);
typeLabel = Phone.getTypeLabel(context.getResources(), type, label);
}
}
+ initials = getInitials(name, nameAlt);
+
if (name == null) {
name = getFormattedNumber(context, number);
}
@@ -260,7 +287,7 @@
name = context.getString(R.string.unknown);
}
- return new PhoneNumberInfo(number, name,
+ return new PhoneNumberInfo(number, name, initials,
TextUtils.isEmpty(photoUriString) ? null : Uri.parse(photoUriString),
typeLabel.toString());
});
@@ -312,31 +339,50 @@
}
/**
- * Sets a Contact avatar onto the provided {@code icon}. The first letter of the contact's
- * display name or {@code fallbackDisplayName} will be used as a fallback resource if avatar
- * loading fails.
+ * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters
+ * of the contact's initials.
*/
public static void setContactBitmapAsync(
Context context,
- final ImageView icon,
- @Nullable final Contact contact,
- @Nullable final String fallbackDisplayName) {
- Uri avatarUri = contact != null ? contact.getAvatarUri() : null;
- String displayName = contact != null ? contact.getDisplayName() : fallbackDisplayName;
-
- setContactBitmapAsync(context, icon, avatarUri, displayName);
+ @Nullable final ImageView icon,
+ @Nullable final Contact contact) {
+ setContactBitmapAsync(context, icon, contact, null);
}
/**
- * Sets a Contact avatar onto the provided {@code icon}. The first letter of the contact's
- * display name will be used as a fallback resource if avatar loading fails.
+ * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters
+ * of the contact's initials or {@code fallbackDisplayName} will be used as a fallback resource
+ * if avatar loading fails.
*/
public static void setContactBitmapAsync(
Context context,
- final ImageView icon,
- final Uri avatarUri,
- final String displayName) {
- LetterTileDrawable letterTileDrawable = createLetterTile(context, displayName);
+ @Nullable final ImageView icon,
+ @Nullable final Contact contact,
+ @Nullable final String fallbackDisplayName) {
+ Uri avatarUri = contact != null ? contact.getAvatarUri() : null;
+ String initials = contact != null
+ ? contact.getInitials() : fallbackDisplayName.substring(0, 1);
+ String identifier = TextUtils.isEmpty(initials) ? fallbackDisplayName : initials;
+
+ setContactBitmapAsync(context, icon, avatarUri, initials, identifier);
+ }
+
+ /**
+ * Sets a Contact avatar onto the provided {@code icon}. A letter tile base on the contact's
+ * initials and identifier will be used as a fallback resource if avatar loading fails.
+ */
+ public static void setContactBitmapAsync(
+ Context context,
+ @Nullable final ImageView icon,
+ @Nullable final Uri avatarUri,
+ @Nullable final String initials,
+ final String identifier) {
+ if (icon == null) {
+ return;
+ }
+
+ LetterTileDrawable letterTileDrawable = createLetterTile(context, initials,
+ TextUtils.isEmpty(initials) ? identifier : initials);
Glide.with(context)
.load(avatarUri)
@@ -344,11 +390,25 @@
.into(icon);
}
- /** Create a {@link LetterTileDrawable} for the given display name. */
- public static LetterTileDrawable createLetterTile(Context context, String displayName) {
- LetterTileDrawable letterTileDrawable = new LetterTileDrawable(context.getResources());
- letterTileDrawable.setContactDetails(displayName, displayName);
- return letterTileDrawable;
+ /**
+ * Create a {@link LetterTileDrawable} for the given initials.
+ *
+ * @param initials is the letters that will be drawn on the canvas. If it is null, then
+ * an avatar anonymous icon will be drawn
+ * @param identifier will decide the color for the drawable. If null, a default color will
+ * be used.
+ */
+ public static LetterTileDrawable createLetterTile(
+ Context context,
+ @Nullable String initials,
+ @Nullable String identifier) {
+ int numberOfLetter = context.getResources().getInteger(
+ R.integer.config_number_of_letters_shown_for_avatar);
+ String letters = initials != null
+ ? initials.substring(0, Math.min(initials.length(), numberOfLetter)) : null;
+ LetterTileDrawable letterTileDrawable = new LetterTileDrawable(context.getResources(),
+ letters, identifier);
+ return letterTileDrawable;
}
/** Set the given phone number as the primary phone number for its associated contact. */
@@ -419,6 +479,19 @@
}
}
+ static String getInitials(String name, String nameAlt) {
+ StringBuilder initials = new StringBuilder();
+ if (!TextUtils.isEmpty(name) && Character.isLetter(name.charAt(0))) {
+ initials.append(Character.toUpperCase(name.charAt(0)));
+ }
+ if (!TextUtils.isEmpty(nameAlt)
+ && !TextUtils.equals(name, nameAlt)
+ && Character.isLetter(nameAlt.charAt(0))) {
+ initials.append(Character.toUpperCase(nameAlt.charAt(0)));
+ }
+ return initials.toString();
+ }
+
private static Uri makeResourceUri(Context context, int resourceId) {
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)