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