[go: nahoru, domu]

Add get/setExtras to all result/request classes

Test: added
Bug: 118690735
Change-Id: Ib9774be86e34a530f4126a28067317dea0e593b3
diff --git a/textclassifier/api/1.0.0-alpha01.txt b/textclassifier/api/1.0.0-alpha01.txt
index 7cdb40d..2d0c5d9 100644
--- a/textclassifier/api/1.0.0-alpha01.txt
+++ b/textclassifier/api/1.0.0-alpha01.txt
@@ -7,6 +7,7 @@
     method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(String!);
     method public String getEntityType(int);
     method @IntRange(from=0) public int getEntityTypeCount();
+    method public android.os.Bundle getExtras();
     method public String? getId();
     method public CharSequence? getText();
     method public android.os.Bundle toBundle();
@@ -17,6 +18,7 @@
     method public androidx.textclassifier.TextClassification.Builder addAction(androidx.core.app.RemoteActionCompat);
     method public androidx.textclassifier.TextClassification build();
     method public androidx.textclassifier.TextClassification.Builder! setEntityType(String, @FloatRange(from=0.0, to=1.0) float);
+    method public androidx.textclassifier.TextClassification.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextClassification.Builder setId(String?);
     method public androidx.textclassifier.TextClassification.Builder! setText(CharSequence?);
   }
@@ -25,6 +27,7 @@
     method public static androidx.textclassifier.TextClassification.Request! createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method @IntRange(from=0) public int getEndIndex();
+    method public android.os.Bundle getExtras();
     method public Long? getReferenceTime();
     method @IntRange(from=0) public int getStartIndex();
     method public CharSequence getText();
@@ -35,6 +38,7 @@
     ctor public TextClassification.Request.Builder(CharSequence, @IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextClassification.Request build();
     method public androidx.textclassifier.TextClassification.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
+    method public androidx.textclassifier.TextClassification.Request.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextClassification.Request.Builder setReferenceTime(Long?);
   }
 
@@ -113,6 +117,7 @@
   public final class TextLinks {
     method public int apply(android.text.Spannable, androidx.textclassifier.TextClassifier, androidx.textclassifier.TextLinksParams);
     method public static androidx.textclassifier.TextLinks createFromBundle(android.os.Bundle);
+    method public android.os.Bundle getExtras();
     method public java.util.Collection<androidx.textclassifier.TextLinks.TextLink> getLinks();
     method public android.os.Bundle toBundle();
     field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0
@@ -129,6 +134,7 @@
     method public androidx.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String,java.lang.Float>);
     method public androidx.textclassifier.TextLinks build();
     method public androidx.textclassifier.TextLinks.Builder clearTextLinks();
+    method public androidx.textclassifier.TextLinks.Builder setExtras(android.os.Bundle?);
   }
 
   public static class TextLinks.DefaultTextLinkSpan extends androidx.textclassifier.TextLinks.TextLinkSpan {
@@ -141,6 +147,7 @@
     method public static androidx.textclassifier.TextLinks.Request createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method public androidx.textclassifier.TextClassifier.EntityConfig getEntityConfig();
+    method public android.os.Bundle getExtras();
     method public Long? getReferenceTime();
     method public CharSequence getText();
     method public android.os.Bundle toBundle();
@@ -151,6 +158,7 @@
     method public androidx.textclassifier.TextLinks.Request build();
     method public androidx.textclassifier.TextLinks.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
     method public androidx.textclassifier.TextLinks.Request.Builder setEntityConfig(androidx.textclassifier.TextClassifier.EntityConfig?);
+    method public androidx.textclassifier.TextLinks.Request.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextLinks.Request.Builder setReferenceTime(Long?);
   }
 
@@ -196,6 +204,7 @@
     method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(String!);
     method public String getEntity(int);
     method @IntRange(from=0) public int getEntityCount();
+    method public android.os.Bundle getExtras();
     method public String? getId();
     method public int getSelectionEndIndex();
     method public int getSelectionStartIndex();
@@ -206,6 +215,7 @@
     ctor public TextSelection.Builder(@IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextSelection build();
     method public androidx.textclassifier.TextSelection.Builder setEntityType(String, @FloatRange(from=0.0, to=1.0) float);
+    method public androidx.textclassifier.TextSelection.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextSelection.Builder setId(String?);
   }
 
@@ -213,6 +223,7 @@
     method public static androidx.textclassifier.TextSelection.Request createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method @IntRange(from=0) public int getEndIndex();
+    method public android.os.Bundle getExtras();
     method @IntRange(from=0) public int getStartIndex();
     method public CharSequence getText();
     method public android.os.Bundle toBundle();
@@ -222,6 +233,7 @@
     ctor public TextSelection.Request.Builder(CharSequence, @IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextSelection.Request build();
     method public androidx.textclassifier.TextSelection.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
+    method public androidx.textclassifier.TextSelection.Request.Builder setExtras(android.os.Bundle?);
   }
 
 }
diff --git a/textclassifier/api/current.txt b/textclassifier/api/current.txt
index 7cdb40d..2d0c5d9 100644
--- a/textclassifier/api/current.txt
+++ b/textclassifier/api/current.txt
@@ -7,6 +7,7 @@
     method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(String!);
     method public String getEntityType(int);
     method @IntRange(from=0) public int getEntityTypeCount();
+    method public android.os.Bundle getExtras();
     method public String? getId();
     method public CharSequence? getText();
     method public android.os.Bundle toBundle();
@@ -17,6 +18,7 @@
     method public androidx.textclassifier.TextClassification.Builder addAction(androidx.core.app.RemoteActionCompat);
     method public androidx.textclassifier.TextClassification build();
     method public androidx.textclassifier.TextClassification.Builder! setEntityType(String, @FloatRange(from=0.0, to=1.0) float);
+    method public androidx.textclassifier.TextClassification.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextClassification.Builder setId(String?);
     method public androidx.textclassifier.TextClassification.Builder! setText(CharSequence?);
   }
@@ -25,6 +27,7 @@
     method public static androidx.textclassifier.TextClassification.Request! createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method @IntRange(from=0) public int getEndIndex();
+    method public android.os.Bundle getExtras();
     method public Long? getReferenceTime();
     method @IntRange(from=0) public int getStartIndex();
     method public CharSequence getText();
@@ -35,6 +38,7 @@
     ctor public TextClassification.Request.Builder(CharSequence, @IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextClassification.Request build();
     method public androidx.textclassifier.TextClassification.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
+    method public androidx.textclassifier.TextClassification.Request.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextClassification.Request.Builder setReferenceTime(Long?);
   }
 
@@ -113,6 +117,7 @@
   public final class TextLinks {
     method public int apply(android.text.Spannable, androidx.textclassifier.TextClassifier, androidx.textclassifier.TextLinksParams);
     method public static androidx.textclassifier.TextLinks createFromBundle(android.os.Bundle);
+    method public android.os.Bundle getExtras();
     method public java.util.Collection<androidx.textclassifier.TextLinks.TextLink> getLinks();
     method public android.os.Bundle toBundle();
     field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0
@@ -129,6 +134,7 @@
     method public androidx.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String,java.lang.Float>);
     method public androidx.textclassifier.TextLinks build();
     method public androidx.textclassifier.TextLinks.Builder clearTextLinks();
+    method public androidx.textclassifier.TextLinks.Builder setExtras(android.os.Bundle?);
   }
 
   public static class TextLinks.DefaultTextLinkSpan extends androidx.textclassifier.TextLinks.TextLinkSpan {
@@ -141,6 +147,7 @@
     method public static androidx.textclassifier.TextLinks.Request createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method public androidx.textclassifier.TextClassifier.EntityConfig getEntityConfig();
+    method public android.os.Bundle getExtras();
     method public Long? getReferenceTime();
     method public CharSequence getText();
     method public android.os.Bundle toBundle();
@@ -151,6 +158,7 @@
     method public androidx.textclassifier.TextLinks.Request build();
     method public androidx.textclassifier.TextLinks.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
     method public androidx.textclassifier.TextLinks.Request.Builder setEntityConfig(androidx.textclassifier.TextClassifier.EntityConfig?);
+    method public androidx.textclassifier.TextLinks.Request.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextLinks.Request.Builder setReferenceTime(Long?);
   }
 
@@ -196,6 +204,7 @@
     method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(String!);
     method public String getEntity(int);
     method @IntRange(from=0) public int getEntityCount();
+    method public android.os.Bundle getExtras();
     method public String? getId();
     method public int getSelectionEndIndex();
     method public int getSelectionStartIndex();
@@ -206,6 +215,7 @@
     ctor public TextSelection.Builder(@IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextSelection build();
     method public androidx.textclassifier.TextSelection.Builder setEntityType(String, @FloatRange(from=0.0, to=1.0) float);
+    method public androidx.textclassifier.TextSelection.Builder setExtras(android.os.Bundle?);
     method public androidx.textclassifier.TextSelection.Builder setId(String?);
   }
 
@@ -213,6 +223,7 @@
     method public static androidx.textclassifier.TextSelection.Request createFromBundle(android.os.Bundle);
     method public androidx.core.os.LocaleListCompat? getDefaultLocales();
     method @IntRange(from=0) public int getEndIndex();
+    method public android.os.Bundle getExtras();
     method @IntRange(from=0) public int getStartIndex();
     method public CharSequence getText();
     method public android.os.Bundle toBundle();
@@ -222,6 +233,7 @@
     ctor public TextSelection.Request.Builder(CharSequence, @IntRange(from=0) int, @IntRange(from=0) int);
     method public androidx.textclassifier.TextSelection.Request build();
     method public androidx.textclassifier.TextSelection.Request.Builder setDefaultLocales(androidx.core.os.LocaleListCompat?);
+    method public androidx.textclassifier.TextSelection.Request.Builder setExtras(android.os.Bundle?);
   }
 
 }
diff --git a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
index e1ca0e4a..f74dde0 100644
--- a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
+++ b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/SimpleTextClassifier.java
@@ -77,8 +77,7 @@
                 builder.addLink(
                         spannable.getSpanStart(urlSpan),
                         spannable.getSpanEnd(urlSpan),
-                        Collections.singletonMap(TextClassifier.TYPE_URL, 1.0f),
-                        urlSpan);
+                        Collections.singletonMap(TextClassifier.TYPE_URL, 1.0f));
             }
         }
         return builder.build();
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/LegacyTextClassifierTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/LegacyTextClassifierTest.java
index 6363f1d..18cec4f 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/LegacyTextClassifierTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/LegacyTextClassifierTest.java
@@ -26,7 +26,6 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.text.style.URLSpan;
 
 import androidx.collection.ArraySet;
 import androidx.core.app.RemoteActionCompat;
@@ -165,8 +164,6 @@
         assertThat(textLink.getStart()).isEqualTo(START);
         assertThat(textLink.getEnd()).isEqualTo(text.length());
         assertThat(textLink.getConfidenceScore(TextClassifier.TYPE_URL)).isEqualTo(1.0f);
-        URLSpan urlSpan = textLink.getUrlSpan();
-        assertThat(urlSpan.getURL()).contains(URL);
     }
 
     @Test
@@ -183,8 +180,6 @@
         assertThat(textLink.getStart()).isEqualTo(START);
         assertThat(textLink.getEnd()).isEqualTo(text.length());
         assertThat(textLink.getConfidenceScore(TextClassifier.TYPE_EMAIL)).isEqualTo(1.0f);
-        URLSpan urlSpan = textLink.getUrlSpan();
-        assertThat(urlSpan.getURL()).contains(EMAIL);
     }
 
     @Test
@@ -201,8 +196,6 @@
         assertThat(textLink.getStart()).isEqualTo(START);
         assertThat(textLink.getEnd()).isEqualTo(text.length());
         assertThat(textLink.getConfidenceScore(TextClassifier.TYPE_PHONE)).isEqualTo(1.0f);
-        URLSpan urlSpan = textLink.getUrlSpan();
-        assertThat(urlSpan.getURL()).contains(PHONE_NUMBER);
     }
 
     @Test
@@ -258,7 +251,6 @@
             String entityType = textLink.getEntity(0);
             assertThat(expectedEntities).contains(entityType);
             assertThat(textLink.getConfidenceScore(entityType)).isEqualTo(1.0f);
-            assertThat(textLink.getUrlSpan().getURL()).contains(entityToSpanText(entityType));
             expectedEntities.remove(entityType);
         }
     }
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/TextClassificationTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/TextClassificationTest.java
index 64b3e40..620f94f 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/TextClassificationTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/TextClassificationTest.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.text.SpannableString;
 
@@ -54,6 +55,12 @@
     private static final LocaleListCompat LOCALE_LIST =
             LocaleListCompat.forLanguageTags("en-US,de-DE");
 
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
 
     private static final String PRIMARY_LABEL = "primaryLabel";
     private static final String PRIMARY_DESCRIPTION = "primaryDescription";
@@ -84,15 +91,18 @@
 
     @Test
     public void testBundle() {
-        final TextClassification reference = createExpectedBuilderWithRemoteActions().build();
+        final TextClassification reference = createExpectedBuilderWithRemoteActions()
+                .setExtras(BUNDLE).build();
         // Serialize/deserialize.
         final TextClassification result = TextClassification.createFromBundle(reference.toBundle());
         assertTextClassificationEquals(result, reference);
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
     public void testBundleRequest() {
-        TextClassification.Request reference = createTextClassificationRequest();
+        TextClassification.Request reference = createTextClassificationRequestBuilder()
+                .setExtras(BUNDLE).build();
 
         // Serialize/deserialize.
         TextClassification.Request result = TextClassification.Request.createFromBundle(
@@ -103,6 +113,7 @@
         assertEquals(END_INDEX, result.getEndIndex());
         assertEquals(LOCALE_LIST.toLanguageTags(), result.getDefaultLocales().toLanguageTags());
         assertEquals(REFERENCE_TIME_IN_MS, result.getReferenceTime());
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -122,7 +133,7 @@
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void testToPlatformRequest() {
-        TextClassification.Request request = createTextClassificationRequest();
+        TextClassification.Request request = createTextClassificationRequestBuilder().build();
         android.view.textclassifier.TextClassification.Request platformRequest =
                 (android.view.textclassifier.TextClassification.Request) request.toPlatform();
 
@@ -241,11 +252,10 @@
         assertThat(platformTextClassification.getIntent()).isNull();
     }
 
-    private static TextClassification.Request createTextClassificationRequest() {
+    private static TextClassification.Request.Builder createTextClassificationRequestBuilder() {
         return new TextClassification.Request.Builder(TEXT, START_INDEX, END_INDEX)
                 .setDefaultLocales(LOCALE_LIST)
-                .setReferenceTime(REFERENCE_TIME_IN_MS)
-                .build();
+                .setReferenceTime(REFERENCE_TIME_IN_MS);
     }
 
     private TextClassification.Builder createExpectedBuilder() {
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinkSpanTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinkSpanTest.java
index 10d9fee..b0dfbe6 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinkSpanTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinkSpanTest.java
@@ -102,7 +102,7 @@
 
         final Map<String, Float> scores = new ArrayMap<>();
         scores.put(TextClassifier.TYPE_EMAIL, 1f);
-        mTextLink = new TextLink(0, ENTITY.length(), scores, null);
+        mTextLink = new TextLink(0, ENTITY.length(), scores);
     }
 
     @Test
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
index 764389c..8bae29d 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/TextLinksTest.java
@@ -26,9 +26,9 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
+import android.os.Bundle;
 import android.text.Spannable;
 import android.text.SpannableString;
-import android.text.style.URLSpan;
 
 import androidx.collection.ArrayMap;
 import androidx.core.os.LocaleListCompat;
@@ -43,7 +43,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -64,6 +63,13 @@
 
     private Map<String, Float> mDummyEntityScores;
 
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
     @Before
     public void setup() {
         mDummyEntityScores = new ArrayMap<>();
@@ -91,17 +97,19 @@
         final TextLinks reference = new TextLinks.Builder(FULL_TEXT.toString())
                 .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f))
                 .addLink(5, 12, getEntityScores(.8f, .1f, .5f))
+                .setExtras(BUNDLE)
                 .build();
 
         // Serialize/deserialize.
         final TextLinks result = TextLinks.createFromBundle(reference.toBundle());
 
         assertTextLinks(result);
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
     public void testBundleRequest() {
-        TextLinks.Request reference = createTextLinksRequest();
+        TextLinks.Request reference = createTextLinksRequest().setExtras(BUNDLE).build();
 
         // Serialize/deserialize.
         TextLinks.Request result = TextLinks.Request.createFromBundle(reference.toBundle());
@@ -113,6 +121,7 @@
                 Arrays.asList("default", "excluded")))
                 .containsExactly("included", "default");
         assertThat(result.getReferenceTime()).isEqualTo(REFERENCE_TIME);
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -128,7 +137,7 @@
     @Test
     @SdkSuppress(minSdkVersion = 28)
     public void testConvertToPlatformRequest() {
-        TextLinks.Request request = createTextLinksRequest();
+        TextLinks.Request request = createTextLinksRequest().build();
 
         android.view.textclassifier.TextLinks.Request platformRequest = request.toPlatform();
         assertEquals(FULL_TEXT, platformRequest.getText());
@@ -153,19 +162,6 @@
     }
 
     @Test
-    public void testTextLinksWithUrlSpan() {
-        final String url = "http://www.google.com";
-        final TextLinks textLinks = new TextLinks.Builder(FULL_TEXT.toString())
-                .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f), new URLSpan(url))
-                .build();
-
-        Collection<TextLinks.TextLink> links = textLinks.getLinks();
-        assertThat(links).hasSize(1);
-        TextLinks.TextLink textLink = links.iterator().next();
-        assertThat(textLink.getUrlSpan().getURL()).isEqualTo(url);
-    }
-
-    @Test
     public void testApply_spannable_no_link() {
         SpannableString text = new SpannableString(FULL_TEXT);
         TextLinks textLinks = new TextLinks.Builder(text).build();
@@ -206,7 +202,7 @@
                 .isEqualTo(TextClassifier.TYPE_PHONE);
     }
 
-    private TextLinks.Request createTextLinksRequest() {
+    private TextLinks.Request.Builder createTextLinksRequest() {
         EntityConfig entityConfig = new EntityConfig.Builder()
                 .setIncludedEntityTypes(Arrays.asList("included"))
                 .setExcludedEntityTypes(Arrays.asList("excluded"))
@@ -216,8 +212,7 @@
         return new TextLinks.Request.Builder(FULL_TEXT)
                 .setDefaultLocales(LOCALE_LIST)
                 .setEntityConfig(entityConfig)
-                .setReferenceTime(REFERENCE_TIME)
-                .build();
+                .setReferenceTime(REFERENCE_TIME);
     }
 
     private void assertTextLinks(TextLinks textLinks) {
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/TextSelectionTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/TextSelectionTest.java
index ae15b09..52c7290 100644
--- a/textclassifier/src/androidTest/java/androidx/textclassifier/TextSelectionTest.java
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/TextSelectionTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.os.Build;
+import android.os.Bundle;
 import android.os.LocaleList;
 
 import androidx.core.os.LocaleListCompat;
@@ -48,9 +49,16 @@
     private static final LocaleListCompat LOCALE_LIST =
             LocaleListCompat.forLanguageTags("en-US,de-DE");
 
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
     @Test
     public void testParcel() {
-        TextSelection reference = createTextSelection();
+        TextSelection reference = createTextSelection().setExtras(BUNDLE).build();
 
         // Serialize/deserialize.
         final TextSelection result = TextSelection.createFromBundle(reference.toBundle());
@@ -58,6 +66,7 @@
         assertEquals(START_INDEX, result.getSelectionStartIndex());
         assertEquals(END_INDEX, result.getSelectionEndIndex());
         assertEquals(ID, result.getId());
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
 
         assertThat(result.getEntityCount()).isEqualTo(3);
         assertThat(result.getEntity(0)).isEqualTo(TextClassifier.TYPE_ADDRESS);
@@ -79,6 +88,7 @@
         TextSelection.Request reference =
                 new TextSelection.Request.Builder(text, startIndex, endIndex)
                         .setDefaultLocales(LocaleListCompat.forLanguageTags("en-US,de-DE"))
+                        .setExtras(BUNDLE)
                         .build();
 
         // Serialize/deserialize.
@@ -88,6 +98,7 @@
         assertEquals(startIndex, result.getStartIndex());
         assertEquals(endIndex, result.getEndIndex());
         assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -160,7 +171,7 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O, maxSdkVersion = Build.VERSION_CODES.O_MR1)
     public void testToPlatform_O() {
-        TextSelection reference = createTextSelection();
+        TextSelection reference = createTextSelection().build();
 
         android.view.textclassifier.TextSelection platformTextSelection =
                 (android.view.textclassifier.TextSelection) reference.toPlatform();
@@ -172,7 +183,7 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
     public void testToPlatform_P() {
-        TextSelection reference = createTextSelection();
+        TextSelection reference = createTextSelection().build();
 
         android.view.textclassifier.TextSelection platformTextSelection =
                 (android.view.textclassifier.TextSelection) reference.toPlatform();
@@ -195,12 +206,11 @@
                 .isWithin(EPSILON).of(URL_SCORE);
     }
 
-    private TextSelection createTextSelection() {
+    private TextSelection.Builder createTextSelection() {
         return new TextSelection.Builder(START_INDEX, END_INDEX)
                 .setId(ID)
                 .setEntityType(TextClassifier.TYPE_ADDRESS, ADDRESS_SCORE)
                 .setEntityType(TextClassifier.TYPE_PHONE, PHONE_SCORE)
-                .setEntityType(TextClassifier.TYPE_URL, URL_SCORE)
-                .build();
+                .setEntityType(TextClassifier.TYPE_URL, URL_SCORE);
     }
 }
diff --git a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
index b55e708..1906b4c 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
@@ -16,6 +16,7 @@
 
 package androidx.textclassifier;
 
+import android.os.Build;
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
@@ -38,6 +39,16 @@
 
     private BundleUtils() {}
 
+    /** Compat wrapper for deepCopy. */
+    static Bundle deepCopy(Bundle bundle) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            return bundle.deepCopy();
+        } else {
+            // TODO: actually perform a deep copy.
+            return (Bundle) bundle.clone();
+        }
+    }
+
     /** Serializes a string keyed map to a bundle, or clears it if null is passed. */
     static void putMap(
             @NonNull Bundle bundle, @NonNull String key, @Nullable Map<String, Float> map) {
diff --git a/textclassifier/src/main/java/androidx/textclassifier/LegacyTextClassifier.java b/textclassifier/src/main/java/androidx/textclassifier/LegacyTextClassifier.java
index c07a9ef..439c1f0 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/LegacyTextClassifier.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/LegacyTextClassifier.java
@@ -144,8 +144,7 @@
                 builder.addLink(
                         spannable.getSpanStart(urlSpan),
                         spannable.getSpanEnd(urlSpan),
-                        Collections.singletonMap(entityType, 1.0f),
-                        urlSpan);
+                        Collections.singletonMap(entityType, 1.0f));
             }
         }
     }
diff --git a/textclassifier/src/main/java/androidx/textclassifier/TextClassification.java b/textclassifier/src/main/java/androidx/textclassifier/TextClassification.java
index 9a381f8..134f213 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/TextClassification.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/TextClassification.java
@@ -75,6 +75,7 @@
     private static final String EXTRA_ACTIONS = "actions";
     private static final String EXTRA_ENTITY_CONFIDENCE = "entity_conf";
     private static final String EXTRA_ID = "id";
+    private static final String EXTRA_EXTRAS = "extras";
     private static final IconCompat NO_ICON =
             IconCompat.createWithData(new byte[0], 0, 0);
 
@@ -88,16 +89,19 @@
     @NonNull private final List<RemoteActionCompat> mActions;
     @NonNull private final EntityConfidence mEntityConfidence;
     @Nullable private final String mId;
+    @NonNull private final Bundle mExtras;
 
     TextClassification(
             @Nullable String text,
             @NonNull List<RemoteActionCompat> actions,
             @NonNull EntityConfidence entityConfidence,
-            @Nullable String id) {
+            @Nullable String id,
+            @NonNull Bundle extras) {
         mText = text;
         mActions = actions;
         mEntityConfidence = entityConfidence;
         mId = id;
+        mExtras = extras;
     }
 
     /**
@@ -155,6 +159,19 @@
         return mId;
     }
 
+    /**
+     * Returns the extended, vendor specific data.
+     *
+     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+     * prefer to hold a reference to the returned bundle rather than frequently calling this
+     * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+     * Bundle are not deep copied.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return BundleUtils.deepCopy(mExtras);
+    }
+
     @Override
     public String toString() {
         return String.format(Locale.US,
@@ -173,6 +190,7 @@
         BundleUtils.putRemoteActionList(bundle, EXTRA_ACTIONS, mActions);
         BundleUtils.putMap(bundle, EXTRA_ENTITY_CONFIDENCE, mEntityConfidence.getConfidenceMap());
         bundle.putString(EXTRA_ID, mId);
+        bundle.putBundle(EXTRA_EXTRAS, mExtras);
         return bundle;
     }
 
@@ -184,7 +202,8 @@
     public static TextClassification createFromBundle(@NonNull Bundle bundle) {
         final Builder builder = new Builder()
                 .setText(bundle.getString(EXTRA_TEXT))
-                .setId(bundle.getString(EXTRA_ID));
+                .setId(bundle.getString(EXTRA_ID))
+                .setExtras(bundle.getBundle(EXTRA_EXTRAS));
         for (Map.Entry<String, Float> entityConfidence : BundleUtils.getFloatStringMapOrThrow(
                 bundle, EXTRA_ENTITY_CONFIDENCE).entrySet()) {
             builder.setEntityType(entityConfidence.getKey(), entityConfidence.getValue());
@@ -339,6 +358,7 @@
         @NonNull private List<RemoteActionCompat> mActions = new ArrayList<>();
         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
         @Nullable private String mId;
+        @Nullable private Bundle mExtras;
 
         /**
          * Sets the classified text.
@@ -386,12 +406,22 @@
         }
 
         /**
+         * Sets the extended, vendor specific data.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds and returns a {@link TextClassification} object.
          */
         @NonNull
         public TextClassification build() {
             return new TextClassification(
-                    mText, mActions, new EntityConfidence(mEntityConfidence), mId);
+                    mText, mActions, new EntityConfidence(mEntityConfidence), mId,
+                    mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
         }
     }
 
@@ -411,18 +441,21 @@
         private final int mEndIndex;
         @Nullable private final LocaleListCompat mDefaultLocales;
         @Nullable private final Long mReferenceTime;
+        @NonNull private final Bundle mExtras;
 
         Request(
                 CharSequence text,
                 int startIndex,
                 int endIndex,
                 LocaleListCompat defaultLocales,
-                Long referenceTime) {
+                Long referenceTime,
+                Bundle extras) {
             mText = text;
             mStartIndex = startIndex;
             mEndIndex = endIndex;
             mDefaultLocales = defaultLocales;
             mReferenceTime = referenceTime;
+            mExtras = extras;
         }
 
         /**
@@ -470,6 +503,19 @@
         }
 
         /**
+         * Returns the extended, vendor specific data.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+         * Bundle are not deep copied.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return BundleUtils.deepCopy(mExtras);
+        }
+
+        /**
          * @hide
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -506,6 +552,7 @@
             private final CharSequence mText;
             private final int mStartIndex;
             private final int mEndIndex;
+            private Bundle mExtras;
 
             @Nullable private LocaleListCompat mDefaultLocales;
             @Nullable private Long mReferenceTime = null;
@@ -560,11 +607,23 @@
             }
 
             /**
+             * Sets the extended, vendor specific data.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
              * Builds and returns the request object.
              */
             @NonNull
             public Request build() {
-                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
+                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime,
+                        mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
             }
         }
 
@@ -580,6 +639,7 @@
             bundle.putInt(EXTRA_END_INDEX, mEndIndex);
             BundleUtils.putLocaleList(bundle, EXTRA_DEFAULT_LOCALES, mDefaultLocales);
             BundleUtils.putLong(bundle, EXTRA_REFERENCE_TIME, mReferenceTime);
+            bundle.putBundle(EXTRA_EXTRAS, mExtras);
             return bundle;
         }
 
@@ -592,7 +652,8 @@
                     bundle.getInt(EXTRA_START_INDEX),
                     bundle.getInt(EXTRA_END_INDEX))
                     .setDefaultLocales(BundleUtils.getLocaleList(bundle, EXTRA_DEFAULT_LOCALES))
-                    .setReferenceTime(BundleUtils.getLong(bundle, EXTRA_REFERENCE_TIME));
+                    .setReferenceTime(BundleUtils.getLong(bundle, EXTRA_REFERENCE_TIME))
+                    .setExtras(bundle.getBundle(EXTRA_EXTRAS));
             return builder.build();
         }
     }
diff --git a/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java b/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
index 389110d..349dfa1 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/TextLinks.java
@@ -27,7 +27,6 @@
 import android.text.Spanned;
 import android.text.method.MovementMethod;
 import android.text.style.ClickableSpan;
-import android.text.style.URLSpan;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
@@ -69,9 +68,11 @@
     private static final String LOG_TAG = "TextLinks";
     private static final String EXTRA_FULL_TEXT = "text";
     private static final String EXTRA_LINKS = "links";
+    private static final String EXTRA_EXTRAS = "extras";
 
     private final CharSequence mFullText;
     private final List<TextLink> mLinks;
+    private final Bundle mExtras;
 
     static final Executor sWorkerExecutor = Executors.newFixedThreadPool(1);
     static final MainThreadExecutor sMainThreadExecutor = new MainThreadExecutor();
@@ -112,9 +113,10 @@
     @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE})
     public @interface ApplyStrategy {}
 
-    TextLinks(CharSequence fullText, List<TextLink> links) {
+    TextLinks(CharSequence fullText, List<TextLink> links, Bundle extras) {
         mFullText = fullText;
         mLinks = Collections.unmodifiableList(links);
+        mExtras = extras;
     }
 
     /**
@@ -135,6 +137,19 @@
         return mLinks;
     }
 
+    /**
+     * Returns the extended, vendor specific data.
+     *
+     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+     * prefer to hold a reference to the returned bundle rather than frequently calling this
+     * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+     * Bundle are not deep copied.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return BundleUtils.deepCopy(mExtras);
+    }
+
     @Override
     @NonNull
     public String toString() {
@@ -150,17 +165,22 @@
         final Bundle bundle = new Bundle();
         bundle.putString(EXTRA_FULL_TEXT, mFullText.toString());
         BundleUtils.putTextLinkList(bundle, EXTRA_LINKS, mLinks);
+        bundle.putBundle(EXTRA_EXTRAS, mExtras);
         return bundle;
     }
 
     /**
      * Extracts an TextLinks object from a bundle that was added using {@link #toBundle()}.
+     *
+     * @throws IllegalArgumentException if the bundle is malformed.
      */
     @NonNull
     public static TextLinks createFromBundle(@NonNull Bundle bundle) {
+        Bundle extras = bundle.getBundle(EXTRA_EXTRAS);
         return new TextLinks(
                 bundle.getString(EXTRA_FULL_TEXT),
-                BundleUtils.getTextLinkListOrThrow(bundle, EXTRA_LINKS));
+                BundleUtils.getTextLinkListOrThrow(bundle, EXTRA_LINKS),
+                extras == null ? Bundle.EMPTY : extras);
     }
 
     /**
@@ -200,8 +220,6 @@
         private final EntityConfidence mEntityScores;
         private final int mStart;
         private final int mEnd;
-        // Allows us to fallback to legacy Linkify if necessary. Not parcelled.
-        @Nullable private final URLSpan mUrlSpan;
 
         /**
          * Create a new TextLink.
@@ -211,16 +229,13 @@
          */
         @VisibleForTesting
         @RestrictTo(RestrictTo.Scope.LIBRARY)
-        TextLink(
-                int start, int end,
-                @NonNull Map<String, Float> entityScores, @Nullable URLSpan urlSpan) {
+        TextLink(int start, int end, @NonNull Map<String, Float> entityScores) {
             Preconditions.checkNotNull(entityScores);
             Preconditions.checkArgument(!entityScores.isEmpty());
             Preconditions.checkArgument(start <= end);
             mStart = start;
             mEnd = end;
             mEntityScores = new EntityConfidence(entityScores);
-            mUrlSpan = urlSpan;
         }
 
         /**
@@ -269,21 +284,12 @@
             return mEntityScores.getConfidenceScore(entityType);
         }
 
-        /**
-         * @hide
-         */
-        @Nullable
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        public URLSpan getUrlSpan() {
-            return mUrlSpan;
-        }
-
         @Override
         @NonNull
         public String toString() {
             return String.format(Locale.US,
-                    "TextLink{start=%s, end=%s, entityScores=%s, urlSpan=%s}",
-                    mStart, mEnd, mEntityScores, mUrlSpan);
+                    "TextLink{start=%s, end=%s, entityScores=%s}",
+                    mStart, mEnd, mEntityScores);
         }
 
         /**
@@ -307,8 +313,7 @@
             return new TextLink(
                     bundle.getInt(EXTRA_START),
                     bundle.getInt(EXTRA_END),
-                    BundleUtils.getFloatStringMapOrThrow(bundle, EXTRA_ENTITY_SCORES),
-                    null /* urlSpan */);
+                    BundleUtils.getFloatStringMapOrThrow(bundle, EXTRA_ENTITY_SCORES));
         }
     }
 
@@ -326,18 +331,21 @@
         @Nullable private final LocaleListCompat mDefaultLocales;
         @NonNull private final EntityConfig mEntityConfig;
         @Nullable private Long mReferenceTime = null;
+        @NonNull private final Bundle mExtras;
 
         Request(
                 @NonNull CharSequence text,
                 @Nullable LocaleListCompat defaultLocales,
                 @Nullable EntityConfig entityConfig,
-                @Nullable Long referenceTime) {
+                @Nullable Long referenceTime,
+                @NonNull Bundle extras) {
             mText = text;
             mDefaultLocales = defaultLocales;
             mEntityConfig = entityConfig == null
                     ? new TextClassifier.EntityConfig.Builder().build()
                     : entityConfig;
             mReferenceTime = referenceTime;
+            mExtras = extras;
         }
 
         /**
@@ -377,6 +385,19 @@
         }
 
         /**
+         * Returns the extended, vendor specific data.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+         * Bundle are not deep copied.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return BundleUtils.deepCopy(mExtras);
+        }
+
+        /**
          * A builder for building TextLinks requests.
          */
         public static final class Builder {
@@ -386,6 +407,7 @@
             @Nullable private LocaleListCompat mDefaultLocales;
             @Nullable private EntityConfig mEntityConfig;
             @Nullable private Long mReferenceTime = null;
+            @Nullable private Bundle mExtras;
 
             public Builder(@NonNull CharSequence text) {
                 mText = Preconditions.checkNotNull(text);
@@ -434,12 +456,23 @@
                 mReferenceTime = referenceTime;
                 return this;
             }
+
+            /**
+             * Sets the extended, vendor specific data.
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
             /**
              * Builds and returns the request object.
              */
             @NonNull
             public Request build() {
-                return new Request(mText, mDefaultLocales, mEntityConfig, mReferenceTime);
+                return new Request(mText, mDefaultLocales, mEntityConfig, mReferenceTime,
+                        mExtras == null ? Bundle.EMPTY : mExtras);
             }
 
         }
@@ -455,6 +488,7 @@
             bundle.putBundle(EXTRA_ENTITY_CONFIG, mEntityConfig.toBundle());
             BundleUtils.putLocaleList(bundle, EXTRA_DEFAULT_LOCALES, mDefaultLocales);
             BundleUtils.putLong(bundle, EXTRA_REFERENCE_TIME, mReferenceTime);
+            bundle.putBundle(EXTRA_EXTRAS, mExtras);
             return bundle;
         }
 
@@ -468,6 +502,7 @@
                     .setEntityConfig(
                             EntityConfig.createFromBundle(bundle.getBundle(EXTRA_ENTITY_CONFIG)))
                     .setReferenceTime(BundleUtils.getLong(bundle, EXTRA_REFERENCE_TIME))
+                    .setExtras(bundle.getBundle(EXTRA_EXTRAS))
                     .build();
         }
 
@@ -708,6 +743,7 @@
     public static final class Builder {
         private final CharSequence mFullText;
         private final ArrayList<TextLink> mLinks;
+        @Nullable private Bundle mExtras;
 
         /**
          * Create a new TextLinks.Builder.
@@ -728,7 +764,7 @@
          */
         @NonNull
         public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores) {
-            mLinks.add(new TextLink(start, end, Preconditions.checkNotNull(entityScores), null));
+            mLinks.add(new TextLink(start, end, Preconditions.checkNotNull(entityScores)));
             return this;
         }
 
@@ -736,17 +772,17 @@
          * @hide
          */
         @NonNull
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        public Builder addLink(
-                int start, int end, @NonNull Map<String, Float> entityScores,
-                @Nullable URLSpan urlSpan) {
-            mLinks.add(new TextLink(start, end, Preconditions.checkNotNull(entityScores), urlSpan));
+        Builder addLink(TextLink link) {
+            mLinks.add(Preconditions.checkNotNull(link));
             return this;
         }
 
+        /**
+         * Sets the extended, vendor specific data.
+         */
         @NonNull
-        Builder addLink(TextLink link) {
-            mLinks.add(Preconditions.checkNotNull(link));
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
             return this;
         }
 
@@ -767,7 +803,8 @@
          */
         @NonNull
         public TextLinks build() {
-            return new TextLinks(mFullText, mLinks);
+            return new TextLinks(mFullText, mLinks,
+                    mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
         }
     }
 
diff --git a/textclassifier/src/main/java/androidx/textclassifier/TextSelection.java b/textclassifier/src/main/java/androidx/textclassifier/TextSelection.java
index 0098831..c1291d6 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/TextSelection.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/TextSelection.java
@@ -43,21 +43,25 @@
     private static final String EXTRA_END_INDEX = "end";
     private static final String EXTRA_ENTITY_CONFIDENCE = "entity_conf";
     private static final String EXTRA_ID = "id";
+    private static final String EXTRA_EXTRAS = "extras";
 
     private final int mStartIndex;
     private final int mEndIndex;
     @NonNull private final EntityConfidence mEntityConfidence;
     @Nullable private final String mId;
+    @NonNull private final Bundle mExtras;
 
     TextSelection(
             int startIndex,
             int endIndex,
             @NonNull EntityConfidence entityConfidence,
-            @Nullable String id) {
+            @Nullable String id,
+            @NonNull Bundle extras) {
         mStartIndex = startIndex;
         mEndIndex = endIndex;
         mEntityConfidence = entityConfidence;
         mId = id;
+        mExtras = extras;
     }
 
     /**
@@ -112,6 +116,19 @@
         return mId;
     }
 
+    /**
+     * Returns the extended, vendor specific data.
+     *
+     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+     * prefer to hold a reference to the returned bundle rather than frequently calling this
+     * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+     * Bundle are not deep copied.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return BundleUtils.deepCopy(mExtras);
+    }
+
     @Override
     public String toString() {
         return String.format(
@@ -131,6 +148,7 @@
         bundle.putInt(EXTRA_END_INDEX, mEndIndex);
         BundleUtils.putMap(bundle, EXTRA_ENTITY_CONFIDENCE, mEntityConfidence.getConfidenceMap());
         bundle.putString(EXTRA_ID, mId);
+        bundle.putBundle(EXTRA_EXTRAS, mExtras);
         return bundle;
     }
 
@@ -142,7 +160,8 @@
         final Builder builder = new Builder(
                 bundle.getInt(EXTRA_START_INDEX),
                 bundle.getInt(EXTRA_END_INDEX))
-                .setId(bundle.getString(EXTRA_ID));
+                .setId(bundle.getString(EXTRA_ID))
+                .setExtras(bundle.getBundle(EXTRA_EXTRAS));
         for (Map.Entry<String, Float> entityConfidence : BundleUtils.getFloatStringMapOrThrow(
                 bundle, EXTRA_ENTITY_CONFIDENCE).entrySet()) {
             builder.setEntityType(entityConfidence.getKey(), entityConfidence.getValue());
@@ -209,6 +228,7 @@
         private final int mEndIndex;
         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
         @Nullable private String mId;
+        @Nullable private Bundle mExtras;
 
         /**
          * Creates a builder used to build {@link TextSelection} objects.
@@ -248,12 +268,22 @@
         }
 
         /**
+         * Sets the extended, vendor specific data.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds and returns {@link TextSelection} object.
          */
         @NonNull
         public TextSelection build() {
             return new TextSelection(
-                    mStartIndex, mEndIndex, new EntityConfidence(mEntityConfidence), mId);
+                    mStartIndex, mEndIndex, new EntityConfidence(mEntityConfidence), mId,
+                    mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
         }
     }
 
@@ -272,16 +302,19 @@
         private final int mStartIndex;
         private final int mEndIndex;
         @Nullable private final LocaleListCompat mDefaultLocales;
+        @NonNull private final Bundle mExtras;
 
         Request(
                 CharSequence text,
                 int startIndex,
                 int endIndex,
-                LocaleListCompat defaultLocales) {
+                LocaleListCompat defaultLocales,
+                Bundle extras) {
             mText = text;
             mStartIndex = startIndex;
             mEndIndex = endIndex;
             mDefaultLocales = defaultLocales;
+            mExtras = extras;
         }
 
         /**
@@ -319,6 +352,19 @@
         }
 
         /**
+         * Returns the extended, vendor specific data.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
+         * Bundle are not deep copied.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return BundleUtils.deepCopy(mExtras);
+        }
+
+        /**
          * @hide
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -353,6 +399,7 @@
             private final CharSequence mText;
             private final int mStartIndex;
             private final int mEndIndex;
+            private Bundle mExtras;
 
             @Nullable private LocaleListCompat mDefaultLocales;
 
@@ -389,11 +436,23 @@
             }
 
             /**
+             * Sets the extended, vendor specific data.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
              * Builds and returns the request object.
              */
             @NonNull
             public Request build() {
-                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales);
+                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales,
+                        mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
             }
         }
 
@@ -408,6 +467,7 @@
             bundle.putInt(EXTRA_START_INDEX, mStartIndex);
             bundle.putInt(EXTRA_END_INDEX, mEndIndex);
             BundleUtils.putLocaleList(bundle, EXTRA_DEFAULT_LOCALES, mDefaultLocales);
+            bundle.putBundle(EXTRA_EXTRAS, mExtras);
             return bundle;
         }
 
@@ -420,7 +480,8 @@
                     bundle.getString(EXTRA_TEXT),
                     bundle.getInt(EXTRA_START_INDEX),
                     bundle.getInt(EXTRA_END_INDEX))
-                    .setDefaultLocales(BundleUtils.getLocaleList(bundle, EXTRA_DEFAULT_LOCALES));
+                    .setDefaultLocales(BundleUtils.getLocaleList(bundle, EXTRA_DEFAULT_LOCALES))
+                    .setExtras(bundle.getBundle(EXTRA_EXTRAS));
             final Request request = builder.build();
             return request;
         }