Merge "Run getDisplayNameAndAvatarUri in background thread" into pi-car-dev
diff --git a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
index 5519b56..73a31c9 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
@@ -59,6 +59,17 @@
         return get();
     }
 
+    /**
+     * Returns if the InMemoryPhoneBook is initialized.
+     * get() won't return null or throw if this is true, but it doesn't
+     * indicate whether or not contacts are loaded yet.
+     *
+     * See also: {@link #isLoaded()}
+     */
+    public static boolean isInitialized() {
+        return sInMemoryPhoneBook != null;
+    }
+
     /** Get the global {@link InMemoryPhoneBook} instance. */
     public static InMemoryPhoneBook get() {
         if (sInMemoryPhoneBook != null) {
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 d4dc7c2..7cb60ae 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
@@ -38,7 +38,6 @@
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
 
 import com.android.car.apps.common.LetterTileDrawable;
 
@@ -51,59 +50,16 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
 
 /** Helper methods. */
 public class TelecomUtils {
     private static final String TAG = "CD.TelecomUtils";
 
-    private static final String[] CONTACT_ID_PROJECTION = new String[]{
-            PhoneLookup.DISPLAY_NAME,
-            PhoneLookup.TYPE,
-            PhoneLookup.LABEL,
-            PhoneLookup._ID
-    };
-
     private static String sVoicemailNumber;
     private static TelephonyManager sTelephonyManager;
 
     /**
-     * Return the label for the given phone number.
-     *
-     * @param number Caller phone number
-     * @return the label if it is found, empty string otherwise.
-     */
-    public static CharSequence getTypeFromNumber(Context context, String number) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getTypeFromNumber, number: " + number);
-        }
-
-        String defaultLabel = "";
-        if (TextUtils.isEmpty(number)) {
-            return defaultLabel;
-        }
-
-        ContentResolver cr = context.getContentResolver();
-        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
-        Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null);
-
-        try {
-            if (cursor != null && cursor.moveToFirst()) {
-                int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
-                int type = cursor.getInt(typeColumn);
-                int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
-                String label = cursor.getString(labelColumn);
-                CharSequence typeLabel = Phone.getTypeLabel(context.getResources(), type, label);
-                return typeLabel;
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-        return defaultLabel;
-    }
-
-    /**
      * Get the voicemail number.
      */
     public static String getVoicemailNumber(Context context) {
@@ -185,59 +141,129 @@
     }
 
     /**
-     * Get the display name and photo uri of the given number (e.g. if it's the voicemail number,
-     * return a string and a uri that represents voicemail, if it's a contact, get the contact's
-     * name and its avatar uri, etc).
-     *
-     * @return Pair of display name and contact's photo uri if found. Voicemail number uses drawable
-     * resource uri and null uri for other cases.
+     * Contains all the info used to display a phone number on the screen.
+     * Returned by {@link #getPhoneNumberInfo(Context, String)}
      */
-    public static Pair<String, Uri> getDisplayNameAndAvatarUri(Context context, String number) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getDisplayNameAndAvatarUri: " + number);
+    public static final class PhoneNumberInfo {
+        private final String mPhoneNumber;
+        private final String mDisplayName;
+        private final Uri mAvatarUri;
+        private final String mTypeLabel;
+
+        public PhoneNumberInfo(String phoneNumber, String displayName,
+                Uri avatarUri, String typeLabel) {
+            mPhoneNumber = phoneNumber;
+            mDisplayName = displayName;
+            mAvatarUri = avatarUri;
+            mTypeLabel = typeLabel;
         }
 
+        public String getPhoneNumber() {
+            return mPhoneNumber;
+        }
+
+        public String getDisplayName() {
+            return mDisplayName;
+        }
+
+        public Uri getAvatarUri() {
+            return mAvatarUri;
+        }
+
+        public String getTypeLabel() {
+            return mTypeLabel;
+        }
+
+    }
+
+    /**
+     * Gets all the info needed to properly display a phone number to the UI. (e.g. if it's the
+     * voicemail number, return a string and a uri that represents voicemail, if it's a contact, get
+     * the contact's name, its avatar uri, the phone number's label, etc).
+     */
+    public static CompletableFuture<PhoneNumberInfo> getPhoneNumberInfo(
+            Context context, String number) {
+
         if (TextUtils.isEmpty(number)) {
-            return new Pair<>(context.getString(R.string.unknown), null);
+            return CompletableFuture.completedFuture(new PhoneNumberInfo(
+                    number,
+                    context.getString(R.string.unknown),
+                    null,
+                    ""));
         }
 
         if (isVoicemailNumber(context, number)) {
-            return new Pair<>(
+            return CompletableFuture.completedFuture(new PhoneNumberInfo(
+                    number,
                     context.getString(R.string.voicemail),
-                    makeResourceUri(context, R.drawable.ic_voicemail));
+                    makeResourceUri(context, R.drawable.ic_voicemail),
+                    ""));
         }
 
-        ContentResolver cr = context.getContentResolver();
-        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+        if (InMemoryPhoneBook.isInitialized()) {
+            Contact contact = InMemoryPhoneBook.get().lookupContactEntry(number);
+            if (contact != null) {
+                String name = contact.getDisplayName();
+                if (name == null) {
+                    name = getFormattedNumber(context, number);
+                }
 
-        Cursor cursor = null;
-        String name = null;
-        String photoUriString = null;
-        try {
-            cursor = cr.query(uri, new String[]{PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_URI},
-                    null, null, null);
-            if (cursor != null && cursor.moveToFirst()) {
-                name = cursor.getString(0);
-                photoUriString = cursor.getString(1);
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
+                if (name == null) {
+                    name = context.getString(R.string.unknown);
+                }
+
+                PhoneNumber phoneNumber = contact.getPhoneNumber(number);
+                CharSequence typeLabel = "";
+                if (phoneNumber != null) {
+                    typeLabel = Phone.getTypeLabel(context.getResources(),
+                            phoneNumber.getType(),
+                            phoneNumber.getLabel());
+                }
+
+                return CompletableFuture.completedFuture(new PhoneNumberInfo(
+                        number,
+                        name,
+                        contact.getAvatarUri(),
+                        typeLabel.toString()));
             }
         }
 
-        if (name == null) {
-            name = getFormattedNumber(context, number);
-        }
+        return CompletableFuture.supplyAsync(() -> {
+            String name = null;
+            String photoUriString = null;
+            CharSequence typeLabel = "";
+            ContentResolver cr = context.getContentResolver();
+            try (Cursor cursor = cr.query(
+                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+                    new String[] {
+                            PhoneLookup.DISPLAY_NAME,
+                            PhoneLookup.PHOTO_URI,
+                            PhoneLookup.TYPE,
+                            PhoneLookup.LABEL,
+                    },
+                    null, null, null)) {
 
-        if (name == null) {
-            name = context.getString(R.string.unknown);
-        }
+                if (cursor != null && cursor.moveToFirst()) {
+                    name = cursor.getString(0);
+                    photoUriString = cursor.getString(1);
+                    int type = cursor.getInt(2);
+                    String label = cursor.getString(3);
+                    typeLabel = Phone.getTypeLabel(context.getResources(), type, label);
+                }
+            }
 
-        if (TextUtils.isEmpty(photoUriString)) {
-            return new Pair<>(name, null);
-        }
-        return new Pair<>(name, Uri.parse(photoUriString));
+            if (name == null) {
+                name = getFormattedNumber(context, number);
+            }
+
+            if (name == null) {
+                name = context.getString(R.string.unknown);
+            }
+
+            return new PhoneNumberInfo(number, name,
+                    TextUtils.isEmpty(photoUriString) ? null : Uri.parse(photoUriString),
+                    typeLabel.toString());
+        });
     }
 
     /**