[ET] Update *New* text styling in the context menu.

This makes the "New" text be a superscript, smaller, and colored.

This also makes some minor updates to the SpanApplier:
1) Ability to apply multiple spans to the same region.
2) Changes the doc to say that applying a null span does nothing.

BUG=902140

Change-Id: Ifb49fa091d7a2f8a0e07e5ab8b12299eba33d585
Reviewed-on: https://chromium-review.googlesource.com/c/1393674
Commit-Queue: Donn Denman <donnd@chromium.org>
Reviewed-by: Ted Choc <tedchoc@chromium.org>
Reviewed-by: Theresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#622265}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsCustomContextMenuItem.java b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsCustomContextMenuItem.java
index f99c55f7..0900a9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsCustomContextMenuItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsCustomContextMenuItem.java
@@ -48,7 +48,7 @@
     }
 
     @Override
-    public String getTitle(Context context) {
+    public CharSequence getTitle(Context context) {
         return mTitle;
     }
 
@@ -84,4 +84,4 @@
     public Uri getIconUri() {
         return mIconUri;
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuItem.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuItem.java
index 54f5efc..758bb00 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuItem.java
@@ -8,12 +8,18 @@
 import android.graphics.drawable.Drawable;
 import android.support.annotation.IntDef;
 import android.support.annotation.StringRes;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.SuperscriptSpan;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.DefaultBrowserInfo;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.ui.text.SpanApplier;
+import org.chromium.ui.text.SpanApplier.SpanInfo;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -178,7 +184,7 @@
      * @return Returns a string for the menu item.
      */
     @Override
-    public String getTitle(Context context) {
+    public CharSequence getTitle(Context context) {
         switch (mItem) {
             case Item.OPEN_IN_BROWSER_ID:
                 return DefaultBrowserInfo.getTitleOpenInDefaultBrowser(false);
@@ -187,6 +193,13 @@
                         TemplateUrlService.getInstance()
                                 .getDefaultSearchEngineTemplateUrl()
                                 .getShortName());
+            case Item.OPEN_IN_EPHEMERAL_TAB:
+            case Item.OPEN_IMAGE_IN_EPHEMERAL_TAB:
+                return SpanApplier.applySpans(context.getString(getStringID(mItem)),
+                        new SpanInfo("<new>", "</new>", new SuperscriptSpan(),
+                                new RelativeSizeSpan(0.75f),
+                                new ForegroundColorSpan(ApiCompatibilityUtils.getColor(
+                                        context.getResources(), R.color.modern_blue_600))));
             default:
                 return context.getString(getStringID(mItem));
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
index 5803bee..f6d63ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
@@ -25,7 +25,7 @@
      * @param context The context required to get the title from resources.
      * @return The title of the menu item.
      */
-    String getTitle(Context context);
+    CharSequence getTitle(Context context);
 
     /**
      * Gets the {@link Drawable} icon of a context menu item asynchronously.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
index 60156e8..06bbf7f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
@@ -85,7 +85,7 @@
             }
         }
 
-        final String titleText = menuItem.getTitle(mActivity);
+        final CharSequence titleText = menuItem.getTitle(mActivity);
         viewHolder.mText.setText(titleText);
 
         if (menuItem instanceof ShareContextMenuItem) {
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 7d46e80..5ab29cf 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -2031,7 +2031,7 @@
         Open image in new tab
       </message>
       <message name="IDS_CONTEXTMENU_OPEN_IMAGE_IN_EPHEMERAL_TAB" desc="Context-sensitive menu item to open a quick preview of the selected image.  This is an 'image' variation of the plain 'Sneak peek' menu item.  In English we're currently calling this 'Sneak peek image' which implies that it's a quick preview without commitment (to making a new Tab).  We're also labeling it *New* to draw attention to it when first released. The selected image link will open in an overlay panel on top of the current tab which will go away easily too. [CHAR-LIMIT=30]">
-        *New* Sneak peek image
+        Sneak peek image <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
       </message>
       <message name="IDS_CONTEXTMENU_LOAD_ORIGINAL_IMAGE" desc="Context sensitive menu item for Lite mode low fidelity placeholder images that loads the original version in place. [CHAR-LIMIT=30]">
         Load image
@@ -2058,7 +2058,7 @@
         Open in private tab
       </message>
       <message name="IDS_CONTEXTMENU_OPEN_IN_EPHEMERAL_TAB" desc="Context-sensitive menu item to open a quick preview of the selected link.  In English we're currently calling this 'Sneak peek' which implies that it's a quick preview without commitment (to making a new Tab).  We're also labeling it *New* to draw attention to it when first released. The selected link will open in an overlay panel on top of the current tab which will go away easily too. [CHAR-LIMIT=30]">
-        *New* Sneak peek
+        Sneak peek <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
       </message>
 
       <!-- Swipe refresh -->
diff --git a/ui/android/java/src/org/chromium/ui/text/SpanApplier.java b/ui/android/java/src/org/chromium/ui/text/SpanApplier.java
index 79cb335..dfe1749 100644
--- a/ui/android/java/src/org/chromium/ui/text/SpanApplier.java
+++ b/ui/android/java/src/org/chromium/ui/text/SpanApplier.java
@@ -29,7 +29,7 @@
     public static final class SpanInfo implements Comparable<SpanInfo> {
         final String mStartTag;
         final String mEndTag;
-        final @Nullable Object mSpan;
+        final @Nullable Object[] mSpans;
         int mStartTagIndex;
         int mEndTagIndex;
 
@@ -37,12 +37,23 @@
          * @param startTag The start tag, e.g. "<tos>".
          * @param endTag The end tag, e.g. "</tos>".
          * @param span The span to apply to the text between the start and end tags. May be null,
-         *         then SpanApplier will just remove start and end tags without applying any span.
+         *         then SpanApplier will not apply any span.
          */
         public SpanInfo(String startTag, String endTag, @Nullable Object span) {
             mStartTag = startTag;
             mEndTag = endTag;
-            mSpan = span;
+            mSpans = span == null ? null : new Object[] {span};
+        }
+
+        /**
+         * @param startTag The start tag, e.g. "<tos>".
+         * @param endTag The end tag, e.g. "</tos>".
+         * @param spans A vararg list of spans to be applied.
+         */
+        public SpanInfo(String startTag, String endTag, Object... spans) {
+            mStartTag = startTag;
+            mEndTag = endTag;
+            mSpans = spans;
         }
 
         @Override
@@ -72,6 +83,7 @@
      * @param input The input string.
      * @param spans The Spans which will be applied to the string.
      * @return A SpannableString with the given spans applied.
+     * @throws IllegalArgumentException if the span cannot be applied.
      */
     public static SpannableString applySpans(String input, SpanInfo... spans) {
         for (SpanInfo span : spans) {
@@ -80,7 +92,7 @@
                     span.mStartTagIndex + span.mStartTag.length());
         }
 
-        // Sort the spans from first to last.
+        // Sort the spans from first to last in the order they appear in the input string.
         Arrays.sort(spans);
 
         // Copy the input text to the output, but omit the start and end tags.
@@ -111,8 +123,13 @@
 
         SpannableString spannableString = new SpannableString(output);
         for (SpanInfo span : spans) {
-            if (span.mStartTagIndex != -1 && span.mSpan != null) {
-                spannableString.setSpan(span.mSpan, span.mStartTagIndex, span.mEndTagIndex, 0);
+            if (span.mStartTagIndex == -1 || span.mSpans == null || span.mSpans.length == 0) {
+                continue;
+            }
+
+            for (Object s : span.mSpans) {
+                if (s == null) continue;
+                spannableString.setSpan(s, span.mStartTagIndex, span.mEndTagIndex, 0);
             }
         }
 
diff --git a/ui/android/junit/src/org/chromium/ui/text/SpanApplierTest.java b/ui/android/junit/src/org/chromium/ui/text/SpanApplierTest.java
index 7b8fc7a..61464ca 100644
--- a/ui/android/junit/src/org/chromium/ui/text/SpanApplierTest.java
+++ b/ui/android/junit/src/org/chromium/ui/text/SpanApplierTest.java
@@ -30,7 +30,7 @@
         SpanInfo span = new SpanInfo("<span>", "</span>", new QuoteSpan());
 
         SpannableString expectedOutput = new SpannableString(output);
-        expectedOutput.setSpan(span.mSpan, 12, 17, 0);
+        expectedOutput.setSpan(span.mSpans[0], 12, 17, 0);
         SpannableString actualOutput = SpanApplier.applySpans(input, span);
 
         assertSpannableStringEquality(expectedOutput, actualOutput);
@@ -47,15 +47,29 @@
         SpanInfo elitSpan = new SpanInfo("<elit>", "<endElit>", new ScaleXSpan(1));
 
         SpannableString expectedOutput = new SpannableString(output);
-        expectedOutput.setSpan(linkSpan.mSpan, 6, 11, 0);
-        expectedOutput.setSpan(consSpan.mSpan, 28, 50, 0);
-        expectedOutput.setSpan(elitSpan.mSpan, 51, 62, 0);
+        expectedOutput.setSpan(linkSpan.mSpans[0], 6, 11, 0);
+        expectedOutput.setSpan(consSpan.mSpans[0], 28, 50, 0);
+        expectedOutput.setSpan(elitSpan.mSpans[0], 51, 62, 0);
         SpannableString actualOutput = SpanApplier.applySpans(input, elitSpan, consSpan, linkSpan);
 
         assertSpannableStringEquality(expectedOutput, actualOutput);
     }
 
     @Test
+    public void testVarargSpanInfoConstructor() {
+        String input = "Lorem ipsum <span>dolor</span> sit amet.";
+        String output = "Lorem ipsum dolor sit amet.";
+        SpanInfo multiSpan = new SpanInfo("<span>", "</span>", new QuoteSpan(), new BulletSpan());
+
+        SpannableString expectedOutput = new SpannableString(output);
+        expectedOutput.setSpan(multiSpan.mSpans[0], 12, 17, 0);
+        expectedOutput.setSpan(multiSpan.mSpans[1], 12, 17, 0);
+        SpannableString actualOutput = SpanApplier.applySpans(input, multiSpan);
+
+        assertSpannableStringEquality(expectedOutput, actualOutput);
+    }
+
+    @Test
     public void testEndTagMissingInInput() {
         String input = "Lorem ipsum <span>dolor</> sit amet.";
         SpanInfo span = new SpanInfo("<span>", "</span>", new QuoteSpan());
@@ -113,11 +127,11 @@
     public void testNullSpan() {
         String input = "Lorem <link>ipsum</link> dolor <span>sit</span> amet.";
         SpanInfo linkSpan = new SpanInfo("<link>", "</link>", new QuoteSpan());
-        SpanInfo nullSpan = new SpanInfo("<span>", "</span>", null);
+        SpanInfo nullSpan = new SpanInfo("<span>", "</span>", (Object) null);
 
         String output = "Lorem ipsum dolor sit amet.";
         SpannableString expectedOutput = new SpannableString(output);
-        expectedOutput.setSpan(linkSpan.mSpan, 6, 11, 0);
+        expectedOutput.setSpan(linkSpan.mSpans[0], 6, 11, 0);
         SpannableString actualOutput = SpanApplier.applySpans(input, linkSpan, nullSpan);
 
         assertSpannableStringEquality(expectedOutput, actualOutput);