1 /*
2  * Copyright (C) 2018 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.icu.util.ULocale;
25 import android.os.Bundle;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.os.UserHandle;
29 import android.util.ArrayMap;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.util.Preconditions;
33 
34 import java.util.Locale;
35 import java.util.Map;
36 
37 /**
38  * Represents the result of language detection of a piece of text.
39  * <p>
40  * This contains a list of locales, each paired with a confidence score, sorted in decreasing
41  * order of those scores. E.g., for a given input text, the model may return
42  * {@code [<"en", 0.85>, <"fr", 0.15>]}. This sample result means the model reports that it is
43  * 85% likely that the entire text is in English and 15% likely that the entire text is in French,
44  * etc. It does not mean that 85% of the input is in English and 15% is in French.
45  */
46 public final class TextLanguage implements Parcelable {
47 
48     public static final @android.annotation.NonNull Creator<TextLanguage> CREATOR = new Creator<TextLanguage>() {
49         @Override
50         public TextLanguage createFromParcel(Parcel in) {
51             return readFromParcel(in);
52         }
53 
54         @Override
55         public TextLanguage[] newArray(int size) {
56             return new TextLanguage[size];
57         }
58     };
59 
60     static final TextLanguage EMPTY = new Builder().build();
61 
62     @Nullable private final String mId;
63     private final EntityConfidence mEntityConfidence;
64     private final Bundle mBundle;
65 
TextLanguage( @ullable String id, EntityConfidence entityConfidence, Bundle bundle)66     private TextLanguage(
67             @Nullable String id,
68             EntityConfidence entityConfidence,
69             Bundle bundle) {
70         mId = id;
71         mEntityConfidence = entityConfidence;
72         mBundle = bundle;
73     }
74 
75     /**
76      * Returns the id, if one exists, for this object.
77      */
78     @Nullable
getId()79     public String getId() {
80         return mId;
81     }
82 
83     /**
84      * Returns the number of possible locales for the processed text.
85      */
86     @IntRange(from = 0)
getLocaleHypothesisCount()87     public int getLocaleHypothesisCount() {
88         return mEntityConfidence.getEntities().size();
89     }
90 
91     /**
92      * Returns the language locale at the specified index. Locales are ordered from high
93      * confidence to low confidence.
94      * <p>
95      * See {@link #getLocaleHypothesisCount()} for the number of locales available.
96      *
97      * @throws IndexOutOfBoundsException if the specified index is out of range.
98      */
99     @NonNull
getLocale(int index)100     public ULocale getLocale(int index) {
101         return ULocale.forLanguageTag(mEntityConfidence.getEntities().get(index));
102     }
103 
104     /**
105      * Returns the confidence score for the specified language locale. The value ranges from
106      * 0 (low confidence) to 1 (high confidence). 0 indicates that the locale was not found for
107      * the processed text.
108      */
109     @FloatRange(from = 0.0, to = 1.0)
getConfidenceScore(@onNull ULocale locale)110     public float getConfidenceScore(@NonNull ULocale locale) {
111         return mEntityConfidence.getConfidenceScore(locale.toLanguageTag());
112     }
113 
114     /**
115      * Returns a bundle containing non-structured extra information about this result. What is
116      * returned in the extras is specific to the {@link TextClassifier} implementation.
117      *
118      * <p><b>NOTE: </b>Do not modify this bundle.
119      */
120     @NonNull
getExtras()121     public Bundle getExtras() {
122         return mBundle;
123     }
124 
125     @Override
toString()126     public String toString() {
127         return String.format(
128                 Locale.US,
129                 "TextLanguage {id=%s, locales=%s, bundle=%s}",
130                 mId, mEntityConfidence, mBundle);
131     }
132 
133     @Override
describeContents()134     public int describeContents() {
135         return 0;
136     }
137 
138     @Override
writeToParcel(Parcel dest, int flags)139     public void writeToParcel(Parcel dest, int flags) {
140         dest.writeString(mId);
141         mEntityConfidence.writeToParcel(dest, flags);
142         dest.writeBundle(mBundle);
143     }
144 
readFromParcel(Parcel in)145     private static TextLanguage readFromParcel(Parcel in) {
146         return new TextLanguage(
147                 in.readString(),
148                 EntityConfidence.CREATOR.createFromParcel(in),
149                 in.readBundle());
150     }
151 
152     /**
153      * Builder used to build TextLanguage objects.
154      */
155     public static final class Builder {
156 
157         @Nullable private String mId;
158         private final Map<String, Float> mEntityConfidenceMap = new ArrayMap<>();
159         @Nullable private Bundle mBundle;
160 
161         /**
162          * Sets a language locale for the processed text and assigns a confidence score. If the
163          * locale has already been set, this updates it.
164          *
165          * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
166          *      0 implies the locale does not exist for the processed text.
167          *      Values greater than 1 are clamped to 1.
168          */
169         @NonNull
putLocale( @onNull ULocale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore)170         public Builder putLocale(
171                 @NonNull ULocale locale,
172                 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
173             Preconditions.checkNotNull(locale);
174             mEntityConfidenceMap.put(locale.toLanguageTag(), confidenceScore);
175             return this;
176         }
177 
178         /**
179          * Sets an optional id for the TextLanguage object.
180          */
181         @NonNull
setId(@ullable String id)182         public Builder setId(@Nullable String id) {
183             mId = id;
184             return this;
185         }
186 
187         /**
188          * Sets a bundle containing non-structured extra information about the TextLanguage object.
189          */
190         @NonNull
setExtras(@onNull Bundle bundle)191         public Builder setExtras(@NonNull Bundle bundle) {
192             mBundle = Preconditions.checkNotNull(bundle);
193             return this;
194         }
195 
196         /**
197          * Builds and returns a new TextLanguage object.
198          * <p>
199          * If necessary, this method will verify fields, clamp them, and make them immutable.
200          */
201         @NonNull
build()202         public TextLanguage build() {
203             mBundle = mBundle == null ? Bundle.EMPTY : mBundle;
204             return new TextLanguage(
205                     mId,
206                     new EntityConfidence(mEntityConfidenceMap),
207                     mBundle);
208         }
209     }
210 
211     /**
212      * A request object for detecting the language of a piece of text.
213      */
214     public static final class Request implements Parcelable {
215 
216         public static final @android.annotation.NonNull Creator<Request> CREATOR = new Creator<Request>() {
217             @Override
218             public Request createFromParcel(Parcel in) {
219                 return readFromParcel(in);
220             }
221 
222             @Override
223             public Request[] newArray(int size) {
224                 return new Request[size];
225             }
226         };
227 
228         private final CharSequence mText;
229         private final Bundle mExtra;
230         @Nullable private String mCallingPackageName;
231         @UserIdInt
232         private int mUserId = UserHandle.USER_NULL;
233 
Request(CharSequence text, Bundle bundle)234         private Request(CharSequence text, Bundle bundle) {
235             mText = text;
236             mExtra = bundle;
237         }
238 
239         /**
240          * Returns the text to process.
241          */
242         @NonNull
getText()243         public CharSequence getText() {
244             return mText;
245         }
246 
247         /**
248          * Sets the name of the package that is sending this request.
249          * Package-private for SystemTextClassifier's use.
250          * @hide
251          */
252         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setCallingPackageName(@ullable String callingPackageName)253         public void setCallingPackageName(@Nullable String callingPackageName) {
254             mCallingPackageName = callingPackageName;
255         }
256 
257         /**
258          * Returns the name of the package that sent this request.
259          * This returns null if no calling package name is set.
260          */
261         @Nullable
getCallingPackageName()262         public String getCallingPackageName() {
263             return mCallingPackageName;
264         }
265 
266         /**
267          * Sets the id of the user that sent this request.
268          * <p>
269          * Package-private for SystemTextClassifier's use.
270          */
setUserId(@serIdInt int userId)271         void setUserId(@UserIdInt int userId) {
272             mUserId = userId;
273         }
274 
275         /**
276          * Returns the id of the user that sent this request.
277          * @hide
278          */
279         @UserIdInt
getUserId()280         public int getUserId() {
281             return mUserId;
282         }
283 
284         /**
285          * Returns a bundle containing non-structured extra information about this request.
286          *
287          * <p><b>NOTE: </b>Do not modify this bundle.
288          */
289         @NonNull
getExtras()290         public Bundle getExtras() {
291             return mExtra;
292         }
293 
294         @Override
describeContents()295         public int describeContents() {
296             return 0;
297         }
298 
299         @Override
writeToParcel(Parcel dest, int flags)300         public void writeToParcel(Parcel dest, int flags) {
301             dest.writeCharSequence(mText);
302             dest.writeString(mCallingPackageName);
303             dest.writeInt(mUserId);
304             dest.writeBundle(mExtra);
305         }
306 
readFromParcel(Parcel in)307         private static Request readFromParcel(Parcel in) {
308             final CharSequence text = in.readCharSequence();
309             final String callingPackageName = in.readString();
310             final int userId = in.readInt();
311             final Bundle extra = in.readBundle();
312 
313             final Request request = new Request(text, extra);
314             request.setCallingPackageName(callingPackageName);
315             request.setUserId(userId);
316             return request;
317         }
318 
319         /**
320          * A builder for building TextLanguage requests.
321          */
322         public static final class Builder {
323 
324             private final CharSequence mText;
325             @Nullable private Bundle mBundle;
326 
327             /**
328              * Creates a builder to build TextLanguage requests.
329              *
330              * @param text the text to process.
331              */
Builder(@onNull CharSequence text)332             public Builder(@NonNull CharSequence text) {
333                 mText = Preconditions.checkNotNull(text);
334             }
335 
336             /**
337              * Sets a bundle containing non-structured extra information about the request.
338              */
339             @NonNull
setExtras(@onNull Bundle bundle)340             public Builder setExtras(@NonNull Bundle bundle) {
341                 mBundle = Preconditions.checkNotNull(bundle);
342                 return this;
343             }
344 
345             /**
346              * Builds and returns a new TextLanguage request object.
347              * <p>
348              * If necessary, this method will verify fields, clamp them, and make them immutable.
349              */
350             @NonNull
build()351             public Request build() {
352                 return new Request(mText.toString(), mBundle == null ? Bundle.EMPTY : mBundle);
353             }
354         }
355     }
356 }
357