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