Merge "Introduce PrecomputedText version of MessagingTextMessage" into udc-qpr-dev
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 7dda91d..5b6b360 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -410,9 +410,11 @@
         // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
         // if they exist
         final List<MessagingMessage> newMessagingMessages =
-                createMessages(newMessages, false /* isHistoric */);
+                createMessages(newMessages, /* isHistoric= */false,
+                        /* usePrecomputedText= */false);
         final List<MessagingMessage> newHistoricMessagingMessages =
-                createMessages(newHistoricMessages, true /* isHistoric */);
+                createMessages(newHistoricMessages, /* isHistoric= */true,
+                        /* usePrecomputedText= */false);
         // bind it, baby
         bindViews(user, showSpinner, unreadCount,
                 newMessagingMessages,
@@ -981,15 +983,17 @@
      * @param newMessages the messages to parse.
      */
     private List<MessagingMessage> createMessages(
-            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+            List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+            boolean usePrecomputedText) {
         List<MessagingMessage> result = new ArrayList<>();
         for (int i = 0; i < newMessages.size(); i++) {
             Notification.MessagingStyle.Message m = newMessages.get(i);
             MessagingMessage message = findAndRemoveMatchingMessage(m);
             if (message == null) {
-                message = MessagingMessage.createMessage(this, m, mImageResolver);
+                message = MessagingMessage.createMessage(this, m,
+                        mImageResolver, usePrecomputedText);
             }
-            message.setIsHistoric(historic);
+            message.setIsHistoric(isHistoric);
             result.add(message);
         }
         return result;
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 098bce1..c132d6a 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -93,8 +93,9 @@
     }
 
     @Override
-    public boolean setMessage(Notification.MessagingStyle.Message message) {
-        MessagingMessage.super.setMessage(message);
+    public boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
+        MessagingMessage.super.setMessage(message, usePrecomputedText);
         Drawable drawable;
         try {
             Uri uri = message.getDataUri();
@@ -114,32 +115,42 @@
         }
         mDrawable = drawable;
         mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
-        setImageDrawable(drawable);
-        setContentDescription(message.getText());
+        if (!usePrecomputedText) {
+            finalizeInflate();
+        }
         return true;
     }
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m, ImageResolver resolver) {
+            Notification.MessagingStyle.Message m, ImageResolver resolver,
+            boolean usePrecomputedText) {
         MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
         MessagingImageMessage createdMessage = sInstancePool.acquire();
         if (createdMessage == null) {
             createdMessage = (MessagingImageMessage) LayoutInflater.from(
                     layout.getContext()).inflate(
-                            R.layout.notification_template_messaging_image_message,
-                            messagingLinearLayout,
-                            false);
+                    R.layout.notification_template_messaging_image_message,
+                    messagingLinearLayout,
+                    false);
             createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         }
         createdMessage.setImageResolver(resolver);
-        boolean created = createdMessage.setMessage(m);
-        if (!created) {
+        // MessagingImageMessage does not use usePrecomputedText.
+        boolean populated = createdMessage.setMessage(m, /* usePrecomputedText= */false);
+        if (!populated) {
             createdMessage.recycle();
-            return MessagingTextMessage.createMessage(layout, m);
+            return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
         }
         return createdMessage;
     }
 
+
+    @Override
+    public void finalizeInflate() {
+        setImageDrawable(mDrawable);
+        setContentDescription(getMessage().getText());
+    }
+
     private void setImageResolver(ImageResolver resolver) {
         mImageResolver = resolver;
     }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 8345c5c..83557cd 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -178,9 +178,9 @@
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
 
         final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
+                /* isHistoric= */true, /* usePrecomputedText= */ false);
         final List<MessagingMessage> newMessagingMessages =
-                createMessages(newMessages, false /* isHistoric */);
+                createMessages(newMessages, /* isHistoric= */false, /* usePrecomputedText= */false);
         bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
     }
 
@@ -518,15 +518,17 @@
      * @param newMessages the messages to parse.
      */
     private List<MessagingMessage> createMessages(
-            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+            List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+            boolean usePrecomputedText) {
         List<MessagingMessage> result = new ArrayList<>();
         for (int i = 0; i < newMessages.size(); i++) {
             Notification.MessagingStyle.Message m = newMessages.get(i);
             MessagingMessage message = findAndRemoveMatchingMessage(m);
             if (message == null) {
-                message = MessagingMessage.createMessage(this, m, mImageResolver);
+                message = MessagingMessage.createMessage(this, m,
+                        mImageResolver, usePrecomputedText);
             }
-            message.setIsHistoric(historic);
+            message.setIsHistoric(isHistoric);
             result.add(message);
         }
         return result;
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 5ecd3b8..ad90a63 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -34,11 +34,12 @@
     String IMAGE_MIME_TYPE_PREFIX = "image/";
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m, ImageResolver resolver) {
+            Notification.MessagingStyle.Message m, ImageResolver resolver,
+            boolean usePrecomputedText) {
         if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) {
-            return MessagingImageMessage.createMessage(layout, m, resolver);
+            return MessagingImageMessage.createMessage(layout, m, resolver, usePrecomputedText);
         } else {
-            return MessagingTextMessage.createMessage(layout, m);
+            return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
         }
     }
 
@@ -55,9 +56,11 @@
 
     /**
      * Set a message for this view.
+     *
      * @return true if setting the message worked
      */
-    default boolean setMessage(Notification.MessagingStyle.Message message) {
+    default boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
         getState().setMessage(message);
         return true;
     }
@@ -151,4 +154,10 @@
     void setVisibility(int visibility);
 
     int getVisibility();
+
+    /**
+     * Finalize inflation of the MessagingMessages, which should be called on Main Thread.
+     * @hide
+     */
+    void finalizeInflate();
 }
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
index 19791db..bd62aad 100644
--- a/core/java/com/android/internal/widget/MessagingTextMessage.java
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -23,7 +23,9 @@
 import android.app.Notification;
 import android.content.Context;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
@@ -35,10 +37,13 @@
 @RemoteViews.RemoteView
 public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
 
+    private static final String TAG = "MessagingTextMessage";
     private static final MessagingPool<MessagingTextMessage> sInstancePool =
             new MessagingPool<>(20);
     private final MessagingMessageState mState = new MessagingMessageState(this);
 
+    private PrecomputedText mPrecomputedText = null;
+
     public MessagingTextMessage(@NonNull Context context) {
         super(context);
     }
@@ -63,25 +68,32 @@
     }
 
     @Override
-    public boolean setMessage(Notification.MessagingStyle.Message message) {
-        MessagingMessage.super.setMessage(message);
-        setText(message.getText());
+    public boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
+        MessagingMessage.super.setMessage(message, usePrecomputedText);
+        if (usePrecomputedText) {
+            mPrecomputedText = PrecomputedText.create(message.getText(), getTextMetricsParams());
+        } else {
+            setText(message.getText());
+            mPrecomputedText = null;
+        }
+
         return true;
     }
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m) {
+            Notification.MessagingStyle.Message m, boolean usePrecomputedText) {
         MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
         MessagingTextMessage createdMessage = sInstancePool.acquire();
         if (createdMessage == null) {
             createdMessage = (MessagingTextMessage) LayoutInflater.from(
                     layout.getContext()).inflate(
-                            R.layout.notification_template_messaging_text_message,
-                            messagingLinearLayout,
-                            false);
+                    R.layout.notification_template_messaging_text_message,
+                    messagingLinearLayout,
+                    false);
             createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         }
-        createdMessage.setMessage(m);
+        createdMessage.setMessage(m, usePrecomputedText);
         return createdMessage;
     }
 
@@ -135,4 +147,20 @@
     public void setColor(int color) {
         setTextColor(color);
     }
+
+    @Override
+    public void finalizeInflate() {
+        try {
+            setText(mPrecomputedText != null ? mPrecomputedText
+                    : getState().getMessage().getText());
+        } catch (IllegalArgumentException exception) {
+            Log.wtf(
+                    /* tag = */ TAG,
+                    /* msg = */ "PrecomputedText setText failed for TextView:" + this,
+                    /* tr = */ exception
+            );
+            mPrecomputedText = null;
+            setText(getState().getMessage().getText());
+        }
+    }
 }