1 /*
2  * Copyright (C) 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 
17 package android.view.textclassifier;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Bundle;
24 import android.os.LocaleList;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.SpannedString;
28 import android.util.ArrayMap;
29 import android.view.textclassifier.TextClassifier.EntityType;
30 import android.view.textclassifier.TextClassifier.Utils;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.Preconditions;
34 
35 import java.util.Locale;
36 import java.util.Map;
37 import java.util.Objects;
38 
39 /**
40  * Information about where text selection should be.
41  */
42 public final class TextSelection implements Parcelable {
43 
44     private final int mStartIndex;
45     private final int mEndIndex;
46     private final EntityConfidence mEntityConfidence;
47     @Nullable private final String mId;
48     @Nullable
49     private final TextClassification mTextClassification;
50     private final Bundle mExtras;
51 
TextSelection( int startIndex, int endIndex, Map<String, Float> entityConfidence, String id, @Nullable TextClassification textClassification, Bundle extras)52     private TextSelection(
53             int startIndex, int endIndex, Map<String, Float> entityConfidence, String id,
54             @Nullable TextClassification textClassification,
55             Bundle extras) {
56         mStartIndex = startIndex;
57         mEndIndex = endIndex;
58         mEntityConfidence = new EntityConfidence(entityConfidence);
59         mId = id;
60         mTextClassification = textClassification;
61         mExtras = extras;
62     }
63 
64     /**
65      * Returns the start index of the text selection.
66      */
getSelectionStartIndex()67     public int getSelectionStartIndex() {
68         return mStartIndex;
69     }
70 
71     /**
72      * Returns the end index of the text selection.
73      */
getSelectionEndIndex()74     public int getSelectionEndIndex() {
75         return mEndIndex;
76     }
77 
78     /**
79      * Returns the number of entities found in the classified text.
80      */
81     @IntRange(from = 0)
getEntityCount()82     public int getEntityCount() {
83         return mEntityConfidence.getEntities().size();
84     }
85 
86     /**
87      * Returns the entity at the specified index. Entities are ordered from high confidence
88      * to low confidence.
89      *
90      * @throws IndexOutOfBoundsException if the specified index is out of range.
91      * @see #getEntityCount() for the number of entities available.
92      */
93     @NonNull
94     @EntityType
getEntity(int index)95     public String getEntity(int index) {
96         return mEntityConfidence.getEntities().get(index);
97     }
98 
99     /**
100      * Returns the confidence score for the specified entity. The value ranges from
101      * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
102      * classified text.
103      */
104     @FloatRange(from = 0.0, to = 1.0)
getConfidenceScore(@ntityType String entity)105     public float getConfidenceScore(@EntityType String entity) {
106         return mEntityConfidence.getConfidenceScore(entity);
107     }
108 
109     /**
110      * Returns the id, if one exists, for this object.
111      */
112     @Nullable
getId()113     public String getId() {
114         return mId;
115     }
116 
117     /**
118      * Returns the text classification result of the suggested selection span. Enables the text
119      * classification by calling
120      * {@link TextSelection.Request.Builder#setIncludeTextClassification(boolean)}. If the text
121      * classifier does not support it, a {@code null} is returned.
122      *
123      * @see TextSelection.Request.Builder#setIncludeTextClassification(boolean)
124      */
125     @Nullable
getTextClassification()126     public TextClassification getTextClassification() {
127         return mTextClassification;
128     }
129 
130     /**
131      * Returns the extended data.
132      *
133      * <p><b>NOTE: </b>Do not modify this bundle.
134      */
135     @NonNull
getExtras()136     public Bundle getExtras() {
137         return mExtras;
138     }
139 
140     /** @hide */
toBuilder()141     public TextSelection.Builder toBuilder() {
142         return new TextSelection.Builder(mStartIndex, mEndIndex)
143                 .setId(mId)
144                 .setEntityConfidence(mEntityConfidence)
145                 .setTextClassification(mTextClassification)
146                 .setExtras(mExtras);
147     }
148 
149     @Override
toString()150     public String toString() {
151         return String.format(
152                 Locale.US,
153                 "TextSelection {id=%s, startIndex=%d, endIndex=%d, entities=%s}",
154                 mId, mStartIndex, mEndIndex, mEntityConfidence);
155     }
156 
157     /**
158      * Builder used to build {@link TextSelection} objects.
159      */
160     public static final class Builder {
161 
162         private final int mStartIndex;
163         private final int mEndIndex;
164         private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
165         @Nullable private String mId;
166         @Nullable
167         private TextClassification mTextClassification;
168         @Nullable
169         private Bundle mExtras;
170 
171         /**
172          * Creates a builder used to build {@link TextSelection} objects.
173          *
174          * @param startIndex the start index of the text selection.
175          * @param endIndex the end index of the text selection. Must be greater than startIndex
176          */
Builder(@ntRangefrom = 0) int startIndex, @IntRange(from = 0) int endIndex)177         public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
178             Preconditions.checkArgument(startIndex >= 0);
179             Preconditions.checkArgument(endIndex > startIndex);
180             mStartIndex = startIndex;
181             mEndIndex = endIndex;
182         }
183 
184         /**
185          * Sets an entity type for the classified text and assigns a confidence score.
186          *
187          * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
188          *      0 implies the entity does not exist for the classified text.
189          *      Values greater than 1 are clamped to 1.
190          */
191         @NonNull
setEntityType( @onNull @ntityType String type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore)192         public Builder setEntityType(
193                 @NonNull @EntityType String type,
194                 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
195             Objects.requireNonNull(type);
196             mEntityConfidence.put(type, confidenceScore);
197             return this;
198         }
199 
setEntityConfidence(EntityConfidence scores)200         Builder setEntityConfidence(EntityConfidence scores) {
201             mEntityConfidence.clear();
202             mEntityConfidence.putAll(scores.toMap());
203             return this;
204         }
205 
206         /**
207          * Sets an id for the TextSelection object.
208          */
209         @NonNull
setId(@ullable String id)210         public Builder setId(@Nullable String id) {
211             mId = id;
212             return this;
213         }
214 
215         /**
216          * Sets the text classification result of the suggested selection. If
217          * {@link Request#shouldIncludeTextClassification()} is {@code true}, set this value.
218          * Otherwise this value may be set to null. The intention of this method is to avoid
219          * doing expensive work if the client is not interested in such result.
220          *
221          * @return this builder
222          * @see Request#shouldIncludeTextClassification()
223          */
224         @NonNull
setTextClassification(@ullable TextClassification textClassification)225         public Builder setTextClassification(@Nullable TextClassification textClassification) {
226             mTextClassification = textClassification;
227             return this;
228         }
229 
230         /**
231          * Sets the extended data.
232          *
233          * @return this builder
234          */
235         @NonNull
setExtras(@ullable Bundle extras)236         public Builder setExtras(@Nullable Bundle extras) {
237             mExtras = extras;
238             return this;
239         }
240 
241         /**
242          * Builds and returns {@link TextSelection} object.
243          */
244         @NonNull
build()245         public TextSelection build() {
246             return new TextSelection(
247                     mStartIndex, mEndIndex, mEntityConfidence, mId,
248                     mTextClassification, mExtras == null ? Bundle.EMPTY : mExtras);
249         }
250     }
251 
252     /**
253      * A request object for generating TextSelection.
254      */
255     public static final class Request implements Parcelable {
256 
257         private final CharSequence mText;
258         private final int mStartIndex;
259         private final int mEndIndex;
260         @Nullable private final LocaleList mDefaultLocales;
261         private final boolean mDarkLaunchAllowed;
262         private final boolean mIncludeTextClassification;
263         private final Bundle mExtras;
264         @Nullable private SystemTextClassifierMetadata mSystemTcMetadata;
265 
Request( CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales, boolean darkLaunchAllowed, boolean includeTextClassification, Bundle extras)266         private Request(
267                 CharSequence text,
268                 int startIndex,
269                 int endIndex,
270                 LocaleList defaultLocales,
271                 boolean darkLaunchAllowed,
272                 boolean includeTextClassification,
273                 Bundle extras) {
274             mText = text;
275             mStartIndex = startIndex;
276             mEndIndex = endIndex;
277             mDefaultLocales = defaultLocales;
278             mDarkLaunchAllowed = darkLaunchAllowed;
279             mIncludeTextClassification = includeTextClassification;
280             mExtras = extras;
281         }
282 
283         /**
284          * Returns the text providing context for the selected text (which is specified by the
285          * sub sequence starting at startIndex and ending at endIndex).
286          */
287         @NonNull
getText()288         public CharSequence getText() {
289             return mText;
290         }
291 
292         /**
293          * Returns start index of the selected part of text.
294          */
295         @IntRange(from = 0)
getStartIndex()296         public int getStartIndex() {
297             return mStartIndex;
298         }
299 
300         /**
301          * Returns end index of the selected part of text.
302          */
303         @IntRange(from = 0)
getEndIndex()304         public int getEndIndex() {
305             return mEndIndex;
306         }
307 
308         /**
309          * Returns true if the TextClassifier should return selection suggestions when "dark
310          * launched". Otherwise, returns false.
311          *
312          * @hide
313          */
isDarkLaunchAllowed()314         public boolean isDarkLaunchAllowed() {
315             return mDarkLaunchAllowed;
316         }
317 
318         /**
319          * @return ordered list of locale preferences that can be used to disambiguate the
320          * provided text.
321          */
322         @Nullable
getDefaultLocales()323         public LocaleList getDefaultLocales() {
324             return mDefaultLocales;
325         }
326 
327         /**
328          * Returns the name of the package that sent this request.
329          * This returns {@code null} if no calling package name is set.
330          */
331         @Nullable
getCallingPackageName()332         public String getCallingPackageName() {
333             return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null;
334         }
335 
336         /**
337          * Sets the information about the {@link SystemTextClassifier} that sent this request.
338          *
339          * @hide
340          */
341         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setSystemTextClassifierMetadata( @ullable SystemTextClassifierMetadata systemTcMetadata)342         public void setSystemTextClassifierMetadata(
343                 @Nullable SystemTextClassifierMetadata systemTcMetadata) {
344             mSystemTcMetadata = systemTcMetadata;
345         }
346 
347         /**
348          * Returns the information about the {@link SystemTextClassifier} that sent this request.
349          *
350          * @hide
351          */
352         @Nullable
getSystemTextClassifierMetadata()353         public SystemTextClassifierMetadata getSystemTextClassifierMetadata() {
354             return mSystemTcMetadata;
355         }
356 
357         /**
358          * Returns true if the client wants the text classifier to classify the text as well and
359          * include a {@link TextClassification} object in the result.
360          */
shouldIncludeTextClassification()361         public boolean shouldIncludeTextClassification() {
362             return mIncludeTextClassification;
363         }
364 
365         /**
366          * Returns the extended data.
367          *
368          * <p><b>NOTE: </b>Do not modify this bundle.
369          */
370         @NonNull
getExtras()371         public Bundle getExtras() {
372             return mExtras;
373         }
374 
375         /**
376          * A builder for building TextSelection requests.
377          */
378         public static final class Builder {
379 
380             private final CharSequence mText;
381             private final int mStartIndex;
382             private final int mEndIndex;
383             @Nullable private LocaleList mDefaultLocales;
384             private boolean mDarkLaunchAllowed;
385             private boolean mIncludeTextClassification;
386             private Bundle mExtras;
387 
388             /**
389              * @param text text providing context for the selected text (which is specified by the
390              *      sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
391              * @param startIndex start index of the selected part of text
392              * @param endIndex end index of the selected part of text
393              */
Builder( @onNull CharSequence text, @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex)394             public Builder(
395                     @NonNull CharSequence text,
396                     @IntRange(from = 0) int startIndex,
397                     @IntRange(from = 0) int endIndex) {
398                 Utils.checkArgument(text, startIndex, endIndex);
399                 mText = text;
400                 mStartIndex = startIndex;
401                 mEndIndex = endIndex;
402             }
403 
404             /**
405              * @param defaultLocales ordered list of locale preferences that may be used to
406              *      disambiguate the provided text. If no locale preferences exist, set this to null
407              *      or an empty locale list.
408              *
409              * @return this builder.
410              */
411             @NonNull
setDefaultLocales(@ullable LocaleList defaultLocales)412             public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
413                 mDefaultLocales = defaultLocales;
414                 return this;
415             }
416 
417             /**
418              * @param allowed whether or not the TextClassifier should return selection suggestions
419              *      when "dark launched". When a TextClassifier is dark launched, it can suggest
420              *      selection changes that should not be used to actually change the user's
421              *      selection. Instead, the suggested selection is logged, compared with the user's
422              *      selection interaction, and used to generate quality metrics for the
423              *      TextClassifier. Not parceled.
424              *
425              * @return this builder.
426              * @hide
427              */
428             @NonNull
setDarkLaunchAllowed(boolean allowed)429             public Builder setDarkLaunchAllowed(boolean allowed) {
430                 mDarkLaunchAllowed = allowed;
431                 return this;
432             }
433 
434             /**
435              * @param includeTextClassification If true, suggests the TextClassifier to classify the
436              *     text in the suggested selection span and include a TextClassification object in
437              *     the result. The TextClassifier may not support this and in which case,
438              *     {@link TextSelection#getTextClassification()} returns {@code null}.
439              *
440              * @return this builder.
441              * @see TextSelection#getTextClassification()
442              */
443             @NonNull
setIncludeTextClassification(boolean includeTextClassification)444             public Builder setIncludeTextClassification(boolean includeTextClassification) {
445                 mIncludeTextClassification = includeTextClassification;
446                 return this;
447             }
448 
449             /**
450              * Sets the extended data.
451              *
452              * @return this builder
453              */
454             @NonNull
setExtras(@ullable Bundle extras)455             public Builder setExtras(@Nullable Bundle extras) {
456                 mExtras = extras;
457                 return this;
458             }
459 
460             /**
461              * Builds and returns the request object.
462              */
463             @NonNull
build()464             public Request build() {
465                 return new Request(new SpannedString(mText), mStartIndex, mEndIndex,
466                         mDefaultLocales, mDarkLaunchAllowed,
467                         mIncludeTextClassification,
468                         mExtras == null ? Bundle.EMPTY : mExtras);
469             }
470         }
471 
472         @Override
describeContents()473         public int describeContents() {
474             return 0;
475         }
476 
477         @Override
writeToParcel(Parcel dest, int flags)478         public void writeToParcel(Parcel dest, int flags) {
479             dest.writeCharSequence(mText);
480             dest.writeInt(mStartIndex);
481             dest.writeInt(mEndIndex);
482             dest.writeParcelable(mDefaultLocales, flags);
483             dest.writeBundle(mExtras);
484             dest.writeParcelable(mSystemTcMetadata, flags);
485             dest.writeBoolean(mIncludeTextClassification);
486         }
487 
readFromParcel(Parcel in)488         private static Request readFromParcel(Parcel in) {
489             final CharSequence text = in.readCharSequence();
490             final int startIndex = in.readInt();
491             final int endIndex = in.readInt();
492             final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
493             final Bundle extras = in.readBundle();
494             final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
495             final boolean includeTextClassification = in.readBoolean();
496 
497             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
498                     /* darkLaunchAllowed= */ false, includeTextClassification, extras);
499             request.setSystemTextClassifierMetadata(systemTcMetadata);
500             return request;
501         }
502 
503         public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR =
504                 new Parcelable.Creator<Request>() {
505                     @Override
506                     public Request createFromParcel(Parcel in) {
507                         return readFromParcel(in);
508                     }
509 
510                     @Override
511                     public Request[] newArray(int size) {
512                         return new Request[size];
513                     }
514                 };
515     }
516 
517     @Override
describeContents()518     public int describeContents() {
519         return 0;
520     }
521 
522     @Override
writeToParcel(Parcel dest, int flags)523     public void writeToParcel(Parcel dest, int flags) {
524         dest.writeInt(mStartIndex);
525         dest.writeInt(mEndIndex);
526         mEntityConfidence.writeToParcel(dest, flags);
527         dest.writeString(mId);
528         dest.writeBundle(mExtras);
529         dest.writeParcelable(mTextClassification, flags);
530     }
531 
532     public static final @android.annotation.NonNull Parcelable.Creator<TextSelection> CREATOR =
533             new Parcelable.Creator<TextSelection>() {
534                 @Override
535                 public TextSelection createFromParcel(Parcel in) {
536                     return new TextSelection(in);
537                 }
538 
539                 @Override
540                 public TextSelection[] newArray(int size) {
541                     return new TextSelection[size];
542                 }
543             };
544 
TextSelection(Parcel in)545     private TextSelection(Parcel in) {
546         mStartIndex = in.readInt();
547         mEndIndex = in.readInt();
548         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
549         mId = in.readString();
550         mExtras = in.readBundle();
551         mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class);
552     }
553 }
554