Add avatar and small icon in bubble primary button.

Bug: 67605985
Test: NewBubbleIntegrationTest, NewReturnToCallControllerTest
PiperOrigin-RevId: 174089572
Change-Id: Icaeb41482cffe522e09ee1ec068b5d47f476b146
diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java
index 402e002..539dba8 100644
--- a/java/com/android/incallui/InCallServiceImpl.java
+++ b/java/com/android/incallui/InCallServiceImpl.java
@@ -99,7 +99,8 @@
       returnToCallController = new ReturnToCallController(this);
     }
     if (NewReturnToCallController.isEnabled(this)) {
-      newReturnToCallController = new NewReturnToCallController(this);
+      newReturnToCallController =
+          new NewReturnToCallController(this, ContactInfoCache.getInstance(context));
     }
 
     IBinder iBinder = super.onBind(intent);
diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java
index cd69ea1..fa7a45d 100644
--- a/java/com/android/incallui/NewReturnToCallController.java
+++ b/java/com/android/incallui/NewReturnToCallController.java
@@ -19,15 +19,21 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 import android.telecom.CallAudioState;
+import android.text.TextUtils;
+import com.android.contacts.common.util.ContactDisplayUtils;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.lettertile.LetterTileDrawable;
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.telecom.TelecomUtil;
+import com.android.incallui.ContactInfoCache.ContactCacheEntry;
+import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
 import com.android.incallui.InCallPresenter.InCallUiListener;
 import com.android.incallui.audiomode.AudioModeProvider;
 import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener;
@@ -41,6 +47,7 @@
 import com.android.newbubble.NewBubble.ExpansionState;
 import com.android.newbubble.NewBubbleInfo;
 import com.android.newbubble.NewBubbleInfo.Action;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -65,12 +72,15 @@
   private final PendingIntent endCall;
   private final PendingIntent fullScreen;
 
+  private final ContactInfoCache contactInfoCache;
+
   public static boolean isEnabled(Context context) {
     return ConfigProviderBindings.get(context).getBoolean("enable_return_to_call_bubble_v2", false);
   }
 
-  public NewReturnToCallController(Context context) {
+  public NewReturnToCallController(Context context, ContactInfoCache contactInfoCache) {
     this.context = context;
+    this.contactInfoCache = contactInfoCache;
 
     toggleSpeaker = createActionIntent(ReturnToCallActionReceiver.ACTION_TOGGLE_SPEAKER);
     showSpeakerSelect =
@@ -130,6 +140,7 @@
     } else {
       bubble.show();
     }
+    startContactInfoSearch();
   }
 
   @VisibleForTesting
@@ -213,6 +224,8 @@
     // parent call is still there.
     if (!CallList.getInstance().hasNonParentActiveOrBackgroundCall()) {
       hideAndReset();
+    } else {
+      startContactInfoSearch();
     }
   }
 
@@ -233,6 +246,22 @@
     }
   }
 
+  private void startContactInfoSearch() {
+    DialerCall dialerCall = CallList.getInstance().getActiveOrBackgroundCall();
+    if (dialerCall != null) {
+      contactInfoCache.findInfo(
+          dialerCall, false /* isIncoming */, new ReturnToCallContactInfoCacheCallback(this));
+    }
+  }
+
+  private void onPhotoAvatarReceived(@NonNull Drawable photo) {
+    bubble.updatePhotoAvatar(photo);
+  }
+
+  private void onLetterTileAvatarReceived(@NonNull Drawable photo) {
+    bubble.updateAvatar(photo);
+  }
+
   private NewBubbleInfo generateBubbleInfo() {
     return NewBubbleInfo.builder()
         .setPrimaryColor(context.getResources().getColor(R.color.dialer_theme_color, null))
@@ -280,4 +309,72 @@
     toggleSpeaker.setAction(action);
     return PendingIntent.getBroadcast(context, 0, toggleSpeaker, 0);
   }
+
+  @NonNull
+  private LetterTileDrawable createLettleTileDrawable(
+      DialerCall dialerCall, ContactCacheEntry entry) {
+    String preferredName =
+        ContactDisplayUtils.getPreferredDisplayName(
+            entry.namePrimary,
+            entry.nameAlternative,
+            ContactsPreferencesFactory.newContactsPreferences(context));
+    if (TextUtils.isEmpty(preferredName)) {
+      preferredName = entry.number;
+    }
+
+    LetterTileDrawable letterTile = new LetterTileDrawable(context.getResources());
+    letterTile.setCanonicalDialerLetterTileDetails(
+        dialerCall.updateNameIfRestricted(preferredName),
+        entry.lookupKey,
+        LetterTileDrawable.SHAPE_CIRCLE,
+        LetterTileDrawable.getContactTypeFromPrimitives(
+            dialerCall.isVoiceMailNumber(),
+            dialerCall.isSpam(),
+            entry.isBusiness,
+            dialerCall.getNumberPresentation(),
+            dialerCall.isConferenceCall()));
+    return letterTile;
+  }
+
+  private static class ReturnToCallContactInfoCacheCallback implements ContactInfoCacheCallback {
+
+    private final WeakReference<NewReturnToCallController> newReturnToCallControllerWeakReference;
+
+    private ReturnToCallContactInfoCacheCallback(
+        NewReturnToCallController newReturnToCallController) {
+      newReturnToCallControllerWeakReference = new WeakReference<>(newReturnToCallController);
+    }
+
+    @Override
+    public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
+      NewReturnToCallController newReturnToCallController =
+          newReturnToCallControllerWeakReference.get();
+      if (newReturnToCallController == null) {
+        return;
+      }
+      if (entry.photo != null) {
+        newReturnToCallController.onPhotoAvatarReceived(entry.photo);
+      } else {
+        DialerCall dialerCall = CallList.getInstance().getCallById(callId);
+        newReturnToCallController.onLetterTileAvatarReceived(
+            newReturnToCallController.createLettleTileDrawable(dialerCall, entry));
+      }
+    }
+
+    @Override
+    public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
+      NewReturnToCallController newReturnToCallController =
+          newReturnToCallControllerWeakReference.get();
+      if (newReturnToCallController == null) {
+        return;
+      }
+      if (entry.photo != null) {
+        newReturnToCallController.onPhotoAvatarReceived(entry.photo);
+      } else {
+        DialerCall dialerCall = CallList.getInstance().getCallById(callId);
+        newReturnToCallController.onLetterTileAvatarReceived(
+            newReturnToCallController.createLettleTileDrawable(dialerCall, entry));
+      }
+    }
+  }
 }
diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java
index d9b9ae2..226326f 100644
--- a/java/com/android/newbubble/NewBubble.java
+++ b/java/com/android/newbubble/NewBubble.java
@@ -57,6 +57,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.ViewAnimator;
+import com.android.dialer.util.DrawableConverter;
 import com.android.newbubble.NewBubbleInfo.Action;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -113,7 +114,10 @@
             hideAndReset();
           } else {
             doResize(
-                () -> viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_ICON));
+                () ->
+                    viewHolder
+                        .getPrimaryButton()
+                        .setDisplayedChild(ViewHolder.CHILD_INDEX_AVATAR_AND_ICON));
           }
         }
       };
@@ -351,6 +355,32 @@
     updateButtonStates();
   }
 
+  /**
+   * Update the avatar from photo.
+   *
+   * @param avatar the new photo avatar in the bubble's primary button
+   */
+  public void updatePhotoAvatar(@NonNull Drawable avatar) {
+    // Make it round
+    int bubbleSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
+    Drawable roundAvatar =
+        DrawableConverter.getRoundedDrawable(context, avatar, bubbleSize, bubbleSize);
+
+    updateAvatar(roundAvatar);
+  }
+
+  /**
+   * Update the avatar.
+   *
+   * @param avatar the new avatar in the bubble's primary button
+   */
+  public void updateAvatar(@NonNull Drawable avatar) {
+    if (!avatar.equals(currentInfo.getAvatar())) {
+      currentInfo = NewBubbleInfo.from(currentInfo).setAvatar(avatar).build();
+      viewHolder.getPrimaryAvatar().setImageDrawable(currentInfo.getAvatar());
+    }
+  }
+
   /** Returns the currently displayed NewBubbleInfo */
   public NewBubbleInfo getBubbleInfo() {
     return currentInfo;
@@ -525,6 +555,7 @@
   }
 
   private void update() {
+    // Whole primary button background
     RippleDrawable backgroundRipple =
         (RippleDrawable)
             context.getResources().getDrawable(R.drawable.bubble_ripple_circle, context.getTheme());
@@ -532,12 +563,23 @@
         ColorUtils.compositeColors(
             context.getColor(R.color.bubble_primary_background_darken),
             currentInfo.getPrimaryColor());
-    backgroundRipple.getDrawable(0).setTint(primaryTint);
+    backgroundRipple.getDrawable(0).mutate().setTint(primaryTint);
     viewHolder.getPrimaryButton().setBackground(backgroundRipple);
 
+    // Small icon
+    RippleDrawable smallIconBackgroundRipple =
+        (RippleDrawable)
+            context
+                .getResources()
+                .getDrawable(R.drawable.bubble_ripple_circle_small, context.getTheme());
+    smallIconBackgroundRipple
+        .getDrawable(0)
+        .setTint(context.getColor(R.color.bubble_button_text_color_blue));
+    viewHolder.getPrimaryIcon().setBackground(smallIconBackgroundRipple);
     viewHolder.getPrimaryIcon().setImageIcon(currentInfo.getPrimaryIcon());
-    updatePrimaryIconAnimation();
+    viewHolder.getPrimaryAvatar().setImageDrawable(currentInfo.getAvatar());
 
+    updatePrimaryIconAnimation();
     updateButtonStates();
   }
 
@@ -715,13 +757,14 @@
   @VisibleForTesting
   class ViewHolder {
 
-    public static final int CHILD_INDEX_ICON = 0;
+    public static final int CHILD_INDEX_AVATAR_AND_ICON = 0;
     public static final int CHILD_INDEX_TEXT = 1;
 
     private final NewMoveHandler moveHandler;
     private final NewWindowRoot root;
     private final ViewAnimator primaryButton;
     private final ImageView primaryIcon;
+    private final ImageView primaryAvatar;
     private final TextView primaryText;
 
     private final NewCheckableButton fullScreenButton;
@@ -737,6 +780,7 @@
       View contentView = inflater.inflate(R.layout.new_bubble_base, root, true);
       expandedView = contentView.findViewById(R.id.bubble_expanded_layout);
       primaryButton = contentView.findViewById(R.id.bubble_button_primary);
+      primaryAvatar = contentView.findViewById(R.id.bubble_icon_avatar);
       primaryIcon = contentView.findViewById(R.id.bubble_icon_primary);
       primaryText = contentView.findViewById(R.id.bubble_text);
 
@@ -793,6 +837,10 @@
       return primaryIcon;
     }
 
+    public ImageView getPrimaryAvatar() {
+      return primaryAvatar;
+    }
+
     public TextView getPrimaryText() {
       return primaryText;
     }
diff --git a/java/com/android/newbubble/NewBubbleInfo.java b/java/com/android/newbubble/NewBubbleInfo.java
index f615929..44232f3 100644
--- a/java/com/android/newbubble/NewBubbleInfo.java
+++ b/java/com/android/newbubble/NewBubbleInfo.java
@@ -35,6 +35,9 @@
 
   public abstract Icon getPrimaryIcon();
 
+  @Nullable
+  public abstract Drawable getAvatar();
+
   @Px
   public abstract int getStartingYPosition();
 
@@ -61,6 +64,8 @@
 
     public abstract Builder setPrimaryIcon(@NonNull Icon primaryIcon);
 
+    public abstract Builder setAvatar(@Nullable Drawable avatar);
+
     public abstract Builder setStartingYPosition(@Px int startingYPosition);
 
     public abstract Builder setActions(List<Action> actions);
diff --git a/java/com/android/newbubble/res/drawable/bubble_ripple_circle_small.xml b/java/com/android/newbubble/res/drawable/bubble_ripple_circle_small.xml
new file mode 100644
index 0000000..109d1ce
--- /dev/null
+++ b/java/com/android/newbubble/res/drawable/bubble_ripple_circle_small.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+  <item>
+    <shape>
+      <corners android:radius="@dimen/bubble_small_icon_size"/>
+      <solid android:color="@android:color/white"/>
+    </shape>
+  </item>
+</ripple>
diff --git a/java/com/android/newbubble/res/layout/new_bubble_base.xml b/java/com/android/newbubble/res/layout/new_bubble_base.xml
index ef35d74..9174f3f 100644
--- a/java/com/android/newbubble/res/layout/new_bubble_base.xml
+++ b/java/com/android/newbubble/res/layout/new_bubble_base.xml
@@ -36,14 +36,28 @@
         android:background="@drawable/bubble_ripple_circle"
         android:measureAllChildren="false"
         tools:backgroundTint="#FF0000AA">
-      <ImageView
-          android:id="@+id/bubble_icon_primary"
-          android:layout_width="@dimen/bubble_size"
-          android:layout_height="@dimen/bubble_size"
-          android:padding="@dimen/bubble_icon_padding"
-          android:tint="@android:color/white"
-          android:tintMode="src_in"
-          tools:src="@android:drawable/ic_btn_speak_now"/>
+      <RelativeLayout
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/bubble_icon_avatar"
+            android:layout_width="@dimen/bubble_size"
+            android:layout_height="@dimen/bubble_size"
+            tools:src="@android:drawable/ic_btn_speak_now"/>
+        <ImageView
+            android:id="@+id/bubble_icon_primary"
+            android:layout_width="@dimen/bubble_small_icon_size"
+            android:layout_height="@dimen/bubble_small_icon_size"
+            android:layout_alignBottom="@id/bubble_icon_avatar"
+            android:layout_alignEnd="@id/bubble_icon_avatar"
+            android:padding="@dimen/bubble_small_icon_padding"
+            android:tint="@android:color/white"
+            android:tintMode="src_in"
+            android:background="@drawable/bubble_ripple_circle_small"
+            android:measureAllChildren="false"
+            tools:backgroundTint="#FF0000AA"
+            tools:src="@android:drawable/ic_btn_speak_now"/>
+      </RelativeLayout>
       <TextView
           android:id="@+id/bubble_text"
           android:layout_width="wrap_content"
diff --git a/java/com/android/newbubble/res/values/values.xml b/java/com/android/newbubble/res/values/values.xml
index 4bb90af..381b007 100644
--- a/java/com/android/newbubble/res/values/values.xml
+++ b/java/com/android/newbubble/res/values/values.xml
@@ -20,6 +20,7 @@
   <dimen name="bubble_icon_padding">16dp</dimen>
   <dimen name="bubble_move_elevation_change">4dp</dimen>
 
+  <dimen name="bubble_button_icon_padding">16dp</dimen>
   <dimen name="bubble_safe_margin_horizontal">-4dp</dimen>
   <dimen name="bubble_safe_margin_vertical">64dp</dimen>
   <dimen name="bubble_shadow_padding_size_vertical">16dp</dimen>
@@ -29,4 +30,6 @@
   <dimen name="bubble_expanded_width">160dp</dimen>
   <dimen name="bubble_radius">20dp</dimen>
   <dimen name="bubble_expanded_separator_height">4dp</dimen>
+  <dimen name="bubble_small_icon_size">24dp</dimen>
+  <dimen name="bubble_small_icon_padding">4dp</dimen>
 </resources>