Create largeIcon using TelecomUtils API

This will ensure the icons are synchronized between Dialer : Messenger, and
that OEMs can set their icons to be rectangular by setting
contact_avatar_corner_radius_percent to 0.

Bug: 145832093
Test: manual
Change-Id: I4fe686dc450d545e9f36877f3eb3fd090541a874
diff --git a/Android.mk b/Android.mk
index 2c2372f..623b5ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -40,10 +40,12 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     car-apps-common \
+    car-telephony-common \
 
 # Including the resources for the static android libraries allows to pick up their static overlays.
 LOCAL_RESOURCE_DIR += \
-    $(LOCAL_PATH)/../libs/car-apps-common/res
+    $(LOCAL_PATH)/../libs/car-apps-common/res \
+    $(LOCAL_PATH)/../libs/car-telephony-common/res \
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     androidx.annotation_annotation \
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7308708..1f9b6af 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -16,4 +16,5 @@
   -->
 <resources>
     <dimen name="notification_contact_photo_size">300dp</dimen>
+    <dimen name="contact_avatar_corner_radius_percent" format="float">0.5</dimen>
 </resources>
diff --git a/src/com/android/car/messenger/MessengerDelegate.java b/src/com/android/car/messenger/MessengerDelegate.java
index 33615fb..58193d4 100644
--- a/src/com/android/car/messenger/MessengerDelegate.java
+++ b/src/com/android/car/messenger/MessengerDelegate.java
@@ -7,20 +7,15 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothMapClient;
-import android.bluetooth.BluetoothProfile;
-import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
+import android.util.Log;
 import android.widget.Toast;
-
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.app.NotificationCompat;
@@ -28,23 +23,24 @@
 import androidx.core.app.NotificationCompat.MessagingStyle;
 import androidx.core.app.Person;
 import androidx.core.app.RemoteInput;
-
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 import com.android.car.apps.common.LetterTileDrawable;
 import com.android.car.messenger.bluetooth.BluetoothHelper;
 import com.android.car.messenger.bluetooth.BluetoothMonitor;
 import com.android.car.messenger.log.L;
+import com.android.car.telephony.common.TelecomUtils;
 import com.android.internal.annotations.GuardedBy;
-
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.request.RequestOptions;
 import com.bumptech.glide.request.target.SimpleTarget;
 import com.bumptech.glide.request.transition.Transition;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.Predicate;
 
 /** Delegate class responsible for handling messaging service actions */
@@ -57,7 +53,10 @@
     private BluetoothMapClient mBluetoothMapClient;
     private final NotificationManager mNotificationManager;
     private final SmsDatabaseHandler mSmsDatabaseHandler;
+    private final int mBitmapSize;
+    private final float mCornerRadiusPercent;
     private boolean mShouldLoadExistingMessages;
+    private CompletableFuture<Void> mPhoneNumberInfoFuture;
 
     @VisibleForTesting
     final Map<MessageKey, MapMessage> mMessages = new HashMap<>();
@@ -67,6 +66,7 @@
     // Notifications for messages received before this time.
     @VisibleForTesting
     final Map<String, Long> mBTDeviceAddressToConnectionTimestamp = new HashMap<>();
+    final Map<SenderKey, Bitmap> mSenderToLargeIconBitmap = new HashMap<>();
 
     public MessengerDelegate(Context context) {
         mContext = context;
@@ -75,6 +75,11 @@
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mSmsDatabaseHandler = new SmsDatabaseHandler(mContext);
 
+        mBitmapSize =
+            mContext.getResources()
+                .getDimensionPixelSize(R.dimen.notification_contact_photo_size);
+        mCornerRadiusPercent = mContext.getResources()
+            .getFloat(R.dimen.contact_avatar_corner_radius_percent);
         try {
             mShouldLoadExistingMessages =
                     mContext.getResources().getBoolean(R.bool.config_loadExistingMessages);
@@ -216,6 +221,14 @@
         }
     }
 
+    protected void onDestroy() {
+        cleanupMessagesAndNotifications(key -> true);
+
+        if (mPhoneNumberInfoFuture != null) {
+            mPhoneNumberInfoFuture.cancel(true);
+        }
+    }
+
     /**
      * Clears all notifications matching the {@param predicate}. Example method calls are when user
      * wants to clear (a) message notification(s), or when the Bluetooth device that received the
@@ -240,6 +253,7 @@
                 messageKeyMapMessageEntry -> predicate.test(messageKeyMapMessageEntry.getKey()));
         clearNotifications(predicate);
         mNotificationInfos.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
+        mSenderToLargeIconBitmap.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
     }
 
     private void updateNotification(MessageKey messageKey, MapMessage mapMessage) {
@@ -258,72 +272,75 @@
         NotificationInfo notificationInfo = mNotificationInfos.get(senderKey);
         notificationInfo.mMessageKeys.add(messageKey);
 
-        updateNotification(senderKey, notificationInfo);
+        updateNotificationWithIcon(senderKey, notificationInfo);
     }
 
-    private void updateNotification(SenderKey senderKey, NotificationInfo notificationInfo) {
-        final Uri photoUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI,
-                getContactId(mContext.getContentResolver(), notificationInfo.mSenderContactUri));
+    private void updateNotificationWithIcon(SenderKey senderKey,
+        NotificationInfo notificationInfo) {
+        String phoneNumber = getPhoneNumber(notificationInfo.mSenderContactUri);
+        if (mSenderToLargeIconBitmap.get(senderKey) != null || phoneNumber == null) {
+            postNotification(senderKey, notificationInfo);
+        }
 
-        Glide.with(mContext)
-                .asBitmap()
-                .load(photoUri)
-                .apply(RequestOptions.circleCropTransform())
-                .into(new SimpleTarget<Bitmap>() {
-                    @Override
-                    public void onResourceReady(Bitmap bitmap,
+        if (mPhoneNumberInfoFuture != null) {
+            mPhoneNumberInfoFuture.cancel(/* mayInterruptRunning= */ true);
+        }
+
+        LetterTileDrawable errorDrawable = TelecomUtils.createLetterTile(mContext,
+            notificationInfo.mSenderName, notificationInfo.mSenderName);
+
+        mPhoneNumberInfoFuture = TelecomUtils.getPhoneNumberInfo(mContext, phoneNumber)
+            .thenAcceptAsync(phoneNumberInfo -> {
+                if (phoneNumberInfo == null) {
+                    postNotification(senderKey, notificationInfo);
+                }
+                Glide.with(mContext)
+                    .asBitmap()
+                    .load(phoneNumberInfo.getAvatarUri())
+                    .apply(new RequestOptions().override(mBitmapSize).error(errorDrawable))
+                    .into(new SimpleTarget<Bitmap>() {
+                        @Override
+                        public void onResourceReady(Bitmap bitmap,
                             Transition<? super Bitmap> transition) {
-                        sendNotification(bitmap);
-                    }
+                            RoundedBitmapDrawable roundedBitmapDrawable =
+                                RoundedBitmapDrawableFactory
+                                    .create(mContext.getResources(), bitmap);
+                            Icon avatarIcon = TelecomUtils
+                                .createFromRoundedBitmapDrawable(roundedBitmapDrawable, mBitmapSize,
+                                    mCornerRadiusPercent);
+                            mSenderToLargeIconBitmap.put(senderKey, avatarIcon.getBitmap());
+                            postNotification(senderKey, notificationInfo);
+                        }
 
-                    @Override
-                    public void onLoadFailed(@Nullable Drawable fallback) {
-                        sendNotification(null);
-                    }
+                        @Override
+                        public void onLoadFailed(@Nullable Drawable fallback) {
+                            postNotification(senderKey, notificationInfo);
+                        }
 
-                    private void sendNotification(Bitmap bitmap) {
-                        mNotificationManager.notify(
-                                notificationInfo.mNotificationId,
-                                createNotification(senderKey, notificationInfo, bitmap));
-                    }
-                });
+                    });
+            }, mContext.getMainExecutor());
     }
 
-    // TODO: move out to a shared library.
-    protected static int getContactId(ContentResolver cr, String contactUri) {
-        if (TextUtils.isEmpty(contactUri)) {
-            return 0;
-        }
-
-        Uri lookupUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
-                Uri.encode(contactUri));
-        String[] projection = new String[]{ContactsContract.PhoneLookup._ID};
-
-        try (Cursor cursor = cr.query(lookupUri, projection, null, null, null)) {
-            if (cursor != null && cursor.moveToFirst() && cursor.isLast()) {
-                return cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID));
-            } else {
-                L.w(TAG, "Unable to find contact id from phone number.");
-            }
-        }
-
-        return 0;
-    }
-
-    protected void onDestroy() {
-        cleanupMessagesAndNotifications(key -> true);
+    private void postNotification(SenderKey senderKey, NotificationInfo notificationInfo) {
+        mNotificationManager.notify(
+            notificationInfo.mNotificationId,
+            createNotification(senderKey, notificationInfo));
     }
 
     private Notification createNotification(
-            SenderKey senderKey, NotificationInfo notificationInfo, Bitmap bitmap) {
+        SenderKey senderKey, NotificationInfo notificationInfo) {
         String contentText = mContext.getResources().getQuantityString(
                 R.plurals.notification_new_message, notificationInfo.mMessageKeys.size(),
                 notificationInfo.mMessageKeys.size());
         long lastReceiveTime = mMessages.get(notificationInfo.mMessageKeys.getLast())
                 .getReceiveTime();
 
-        if (bitmap == null) {
-            bitmap = letterTileBitmap(notificationInfo.mSenderName);
+        Bitmap largeIcon = mSenderToLargeIconBitmap.get(senderKey);
+        if (largeIcon == null) {
+            largeIcon =
+                TelecomUtils.createLetterTile(mContext,
+                    TelecomUtils.getInitials(notificationInfo.mSenderName, ""),
+                    notificationInfo.mSenderName, mBitmapSize, mCornerRadiusPercent).getBitmap();
         }
 
         final String senderName = notificationInfo.mSenderName;
@@ -358,7 +375,7 @@
                 .setContentText(contentText)
                 .setStyle(messagingStyle)
                 .setCategory(Notification.CATEGORY_MESSAGE)
-                .setLargeIcon(bitmap)
+                .setLargeIcon(largeIcon)
                 .setSmallIcon(R.drawable.ic_message)
                 .setWhen(lastReceiveTime)
                 .setShowWhen(true)
@@ -371,17 +388,6 @@
         return builder.build();
     }
 
-    private Bitmap letterTileBitmap(String senderName) {
-        LetterTileDrawable letterTileDrawable = new LetterTileDrawable(mContext.getResources());
-        letterTileDrawable.setContactDetails(senderName, senderName);
-        letterTileDrawable.setIsCircular(true);
-
-        int bitmapSize = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.notification_contact_photo_size);
-
-        return letterTileDrawable.toBitmap(bitmapSize);
-    }
-
     private PendingIntent createServiceIntent(SenderKey senderKey, int notificationId,
             String action) {
         Intent intent = new Intent(mContext, MessengerService.class)
@@ -449,6 +455,18 @@
     }
 
     /**
+     * Extracts the phone number from the {@link BluetoothMapClient} formatted URI.
+     **/
+    @Nullable
+    private String getPhoneNumber(String senderContactUri) {
+        if (senderContactUri == null || !senderContactUri.matches("tel:(.+)")) {
+            return null;
+        }
+
+        return senderContactUri.substring(4);
+    }
+
+    /**
      * Contains information about a single notification that is displayed, with grouped messages.
      */
     @VisibleForTesting
diff --git a/src/com/android/car/messenger/SmsDatabaseHandler.java b/src/com/android/car/messenger/SmsDatabaseHandler.java
index 333d8d7..77d8e29 100644
--- a/src/com/android/car/messenger/SmsDatabaseHandler.java
+++ b/src/com/android/car/messenger/SmsDatabaseHandler.java
@@ -1,8 +1,6 @@
 package com.android.car.messenger;
 
 
-import static com.android.car.messenger.MessengerDelegate.getContactId;
-
 import android.Manifest;
 import android.app.AppOpsManager;
 import android.content.ContentResolver;
@@ -13,7 +11,9 @@
 import android.database.DatabaseUtils;
 import android.net.Uri;
 import android.provider.BaseColumns;
+import android.provider.ContactsContract;
 import android.provider.Telephony;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.core.content.ContextCompat;
@@ -194,4 +194,25 @@
 
         return granted;
     }
+
+    // TODO: move out to a shared library.
+    private static int getContactId(ContentResolver cr, String contactUri) {
+        if (TextUtils.isEmpty(contactUri)) {
+            return 0;
+        }
+
+        Uri lookupUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+            Uri.encode(contactUri));
+        String[] projection = new String[]{ContactsContract.PhoneLookup._ID};
+
+        try (Cursor cursor = cr.query(lookupUri, projection, null, null, null)) {
+            if (cursor != null && cursor.moveToFirst() && cursor.isLast()) {
+                return cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID));
+            } else {
+                L.w(TAG, "Unable to find contact id from phone number.");
+            }
+        }
+
+        return 0;
+    }
 }