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