blob: ba854e040460facfd46b08b647bc7308c72b8bff [file] [log] [blame]
Richard Ledley68d94522017-10-05 10:52:19 +01001/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.textclassifier;
18
19import android.annotation.FloatRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.os.LocaleList;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010023import android.os.Parcel;
24import android.os.Parcelable;
Richard Ledley68d94522017-10-05 10:52:19 +010025import android.text.SpannableString;
26import android.text.style.ClickableSpan;
Richard Ledley26b87222017-11-30 10:54:08 +000027import android.view.View;
28import android.widget.TextView;
Richard Ledley68d94522017-10-05 10:52:19 +010029
30import com.android.internal.util.Preconditions;
31
32import java.util.ArrayList;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.List;
36import java.util.Map;
37import java.util.function.Function;
38
39/**
40 * A collection of links, representing subsequences of text and the entity types (phone number,
41 * address, url, etc) they may be.
42 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +010043public final class TextLinks implements Parcelable {
Richard Ledley68d94522017-10-05 10:52:19 +010044 private final String mFullText;
45 private final List<TextLink> mLinks;
46
47 private TextLinks(String fullText, Collection<TextLink> links) {
48 mFullText = fullText;
49 mLinks = Collections.unmodifiableList(new ArrayList<>(links));
50 }
51
52 /**
53 * Returns an unmodifiable Collection of the links.
54 */
55 public Collection<TextLink> getLinks() {
56 return mLinks;
57 }
58
59 /**
60 * Annotates the given text with the generated links. It will fail if the provided text doesn't
61 * match the original text used to crete the TextLinks.
62 *
63 * @param text the text to apply the links to. Must match the original text.
64 * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null.
65 *
66 * @return Success or failure.
67 */
68 public boolean apply(
69 @NonNull SpannableString text,
70 @Nullable Function<TextLink, ClickableSpan> spanFactory) {
71 Preconditions.checkNotNull(text);
72 if (!mFullText.equals(text.toString())) {
73 return false;
74 }
75
76 if (spanFactory == null) {
77 spanFactory = DEFAULT_SPAN_FACTORY;
78 }
79 for (TextLink link : mLinks) {
80 final ClickableSpan span = spanFactory.apply(link);
81 if (span != null) {
82 text.setSpan(span, link.getStart(), link.getEnd(), 0);
83 }
84 }
85 return true;
86 }
87
Jan Althaus0d9fbb92017-11-28 12:19:33 +010088 @Override
89 public int describeContents() {
90 return 0;
91 }
92
93 @Override
94 public void writeToParcel(Parcel dest, int flags) {
95 dest.writeString(mFullText);
96 dest.writeTypedList(mLinks);
97 }
98
99 public static final Parcelable.Creator<TextLinks> CREATOR =
100 new Parcelable.Creator<TextLinks>() {
101 @Override
102 public TextLinks createFromParcel(Parcel in) {
103 return new TextLinks(in);
104 }
105
106 @Override
107 public TextLinks[] newArray(int size) {
108 return new TextLinks[size];
109 }
110 };
111
112 private TextLinks(Parcel in) {
113 mFullText = in.readString();
114 mLinks = in.createTypedArrayList(TextLink.CREATOR);
115 }
116
Richard Ledley68d94522017-10-05 10:52:19 +0100117 /**
118 * A link, identifying a substring of text and possible entity types for it.
119 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100120 public static final class TextLink implements Parcelable {
121 private final EntityConfidence mEntityScores;
Richard Ledley68d94522017-10-05 10:52:19 +0100122 private final String mOriginalText;
123 private final int mStart;
124 private final int mEnd;
125
126 /**
127 * Create a new TextLink.
128 *
129 * @throws IllegalArgumentException if entityScores is null or empty.
130 */
131 public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) {
132 Preconditions.checkNotNull(originalText);
133 Preconditions.checkNotNull(entityScores);
134 Preconditions.checkArgument(!entityScores.isEmpty());
135 Preconditions.checkArgument(start <= end);
136 mOriginalText = originalText;
137 mStart = start;
138 mEnd = end;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100139 mEntityScores = new EntityConfidence(entityScores);
Richard Ledley68d94522017-10-05 10:52:19 +0100140 }
141
142 /**
143 * Returns the start index of this link in the original text.
144 *
145 * @return the start index.
146 */
147 public int getStart() {
148 return mStart;
149 }
150
151 /**
152 * Returns the end index of this link in the original text.
153 *
154 * @return the end index.
155 */
156 public int getEnd() {
157 return mEnd;
158 }
159
160 /**
161 * Returns the number of entity types that have confidence scores.
162 *
163 * @return the entity count.
164 */
165 public int getEntityCount() {
166 return mEntityScores.getEntities().size();
167 }
168
169 /**
170 * Returns the entity type at a given index. Entity types are sorted by confidence.
171 *
172 * @return the entity type at the provided index.
173 */
174 @NonNull public @TextClassifier.EntityType String getEntity(int index) {
175 return mEntityScores.getEntities().get(index);
176 }
177
178 /**
179 * Returns the confidence score for a particular entity type.
180 *
181 * @param entityType the entity type.
182 */
183 public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
184 @TextClassifier.EntityType String entityType) {
185 return mEntityScores.getConfidenceScore(entityType);
186 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100187
188 @Override
189 public int describeContents() {
190 return 0;
191 }
192
193 @Override
194 public void writeToParcel(Parcel dest, int flags) {
195 mEntityScores.writeToParcel(dest, flags);
196 dest.writeString(mOriginalText);
197 dest.writeInt(mStart);
198 dest.writeInt(mEnd);
199 }
200
201 public static final Parcelable.Creator<TextLink> CREATOR =
202 new Parcelable.Creator<TextLink>() {
203 @Override
204 public TextLink createFromParcel(Parcel in) {
205 return new TextLink(in);
206 }
207
208 @Override
209 public TextLink[] newArray(int size) {
210 return new TextLink[size];
211 }
212 };
213
214 private TextLink(Parcel in) {
215 mEntityScores = EntityConfidence.CREATOR.createFromParcel(in);
216 mOriginalText = in.readString();
217 mStart = in.readInt();
218 mEnd = in.readInt();
219 }
Richard Ledley68d94522017-10-05 10:52:19 +0100220 }
221
222 /**
223 * Optional input parameters for generating TextLinks.
224 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100225 public static final class Options implements Parcelable {
Richard Ledley68d94522017-10-05 10:52:19 +0100226
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000227 private LocaleList mDefaultLocales;
Richard Ledleydb18a572017-11-30 17:33:51 +0000228 private TextClassifier.EntityConfig mEntityConfig;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000229
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100230 public Options() {}
231
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000232 /**
Richard Ledleydb18a572017-11-30 17:33:51 +0000233 * @param defaultLocales ordered list of locale preferences that may be used to
234 * disambiguate the provided text. If no locale preferences exist,
235 * set this to null or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000236 */
237 public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
238 mDefaultLocales = defaultLocales;
239 return this;
Richard Ledley68d94522017-10-05 10:52:19 +0100240 }
241
242 /**
Richard Ledleydb18a572017-11-30 17:33:51 +0000243 * Sets the entity configuration to use. This determines what types of entities the
244 * TextClassifier will look for.
245 *
246 * @param entityConfig EntityConfig to use
247 */
248 public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
249 mEntityConfig = entityConfig;
250 return this;
251 }
252
253 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000254 * @return ordered list of locale preferences that can be used to disambiguate
255 * the provided text.
Richard Ledley68d94522017-10-05 10:52:19 +0100256 */
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000257 @Nullable
258 public LocaleList getDefaultLocales() {
259 return mDefaultLocales;
Richard Ledley68d94522017-10-05 10:52:19 +0100260 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000261
262 /**
263 * @return The config representing the set of entities to look for.
264 * @see #setEntityConfig(TextClassifier.EntityConfig)
265 */
266 @Nullable
267 public TextClassifier.EntityConfig getEntityConfig() {
268 return mEntityConfig;
269 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100270
271 @Override
272 public int describeContents() {
273 return 0;
274 }
275
276 @Override
277 public void writeToParcel(Parcel dest, int flags) {
278 dest.writeInt(mDefaultLocales != null ? 1 : 0);
279 if (mDefaultLocales != null) {
280 mDefaultLocales.writeToParcel(dest, flags);
281 }
282 dest.writeInt(mEntityConfig != null ? 1 : 0);
283 if (mEntityConfig != null) {
284 mEntityConfig.writeToParcel(dest, flags);
285 }
286 }
287
288 public static final Parcelable.Creator<Options> CREATOR =
289 new Parcelable.Creator<Options>() {
290 @Override
291 public Options createFromParcel(Parcel in) {
292 return new Options(in);
293 }
294
295 @Override
296 public Options[] newArray(int size) {
297 return new Options[size];
298 }
299 };
300
301 private Options(Parcel in) {
302 if (in.readInt() > 0) {
303 mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
304 }
305 if (in.readInt() > 0) {
306 mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
307 }
308 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000309 }
Richard Ledley68d94522017-10-05 10:52:19 +0100310
311 /**
312 * A function to create spans from TextLinks.
313 *
314 * Applies only to TextViews.
315 * We can hide this until we are convinced we want it to be part of the public API.
316 *
317 * @hide
318 */
319 public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
Richard Ledley26b87222017-11-30 10:54:08 +0000320 textLink -> new ClickableSpan() {
321 @Override
322 public void onClick(View widget) {
323 if (widget instanceof TextView) {
324 final TextView textView = (TextView) widget;
325 textView.requestActionMode(textLink);
326 }
327 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000328 };
Richard Ledley68d94522017-10-05 10:52:19 +0100329
330 /**
331 * A builder to construct a TextLinks instance.
332 */
333 public static final class Builder {
334 private final String mFullText;
335 private final Collection<TextLink> mLinks;
336
337 /**
338 * Create a new TextLinks.Builder.
339 *
340 * @param fullText The full text that links will be added to.
341 */
342 public Builder(@NonNull String fullText) {
343 mFullText = Preconditions.checkNotNull(fullText);
344 mLinks = new ArrayList<>();
345 }
346
347 /**
348 * Adds a TextLink.
349 *
350 * @return this instance.
351 */
352 public Builder addLink(TextLink link) {
353 Preconditions.checkNotNull(link);
354 mLinks.add(link);
355 return this;
356 }
357
358 /**
359 * Constructs a TextLinks instance.
360 *
361 * @return the constructed TextLinks.
362 */
363 public TextLinks build() {
364 return new TextLinks(mFullText, mLinks);
365 }
366 }
367}