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