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