1 /*
2  * Copyright (C) 2022 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.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SuppressLint;
22 import android.graphics.RectF;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.widget.TextView;
26 
27 import java.util.Objects;
28 
29 /**
30  * A subclass of {@link HandwritingGesture} for selecting a range of text by defining start and end
31  * rectangles. This can be useful when the range cannot be defined with a single rectangle.
32  * This class holds the information required for selection of text in
33  * toolkit widgets like {@link TextView}.
34  * <p>Note: this selects text within a range <em>between</em> two given areas. To select all text
35  * <em>within</em> a single area, use {@link SelectGesture}</p>
36  */
37 public final class SelectRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
38 
39     private @Granularity int mGranularity;
40     private RectF mStartArea;
41     private RectF mEndArea;
42 
SelectRangeGesture( int granularity, RectF startArea, RectF endArea, String fallbackText)43     private SelectRangeGesture(
44             int granularity, RectF startArea, RectF endArea, String fallbackText) {
45         mType = GESTURE_TYPE_SELECT_RANGE;
46         mStartArea = startArea;
47         mEndArea = endArea;
48         mGranularity = granularity;
49         mFallbackText = fallbackText;
50     }
51 
SelectRangeGesture(@onNull Parcel source)52     private SelectRangeGesture(@NonNull Parcel source) {
53         mType = GESTURE_TYPE_SELECT_RANGE;
54         mFallbackText = source.readString8();
55         mGranularity = source.readInt();
56         mStartArea = source.readTypedObject(RectF.CREATOR);
57         mEndArea = source.readTypedObject(RectF.CREATOR);
58     }
59 
60     /**
61      * Returns Granular level on which text should be operated.
62      * @see #GRANULARITY_CHARACTER
63      * @see #GRANULARITY_WORD
64      */
65     @Granularity
getGranularity()66     public int getGranularity() {
67         return mGranularity;
68     }
69 
70     /**
71      * Returns the Selection start area {@link RectF} in screen coordinates.
72      *
73      * Getter for selection area set with {@link Builder#setSelectionStartArea(RectF)}.
74      */
75     @NonNull
getSelectionStartArea()76     public RectF getSelectionStartArea() {
77         return mStartArea;
78     }
79 
80     /**
81      * Returns the Selection end area {@link RectF} in screen coordinates.
82      *
83      * Getter for selection area set with {@link Builder#setSelectionEndArea(RectF)}.
84      */
85     @NonNull
getSelectionEndArea()86     public RectF getSelectionEndArea() {
87         return mEndArea;
88     }
89 
90     /**
91      * Builder for {@link SelectRangeGesture}. This class is not designed to be thread-safe.
92      */
93     public static final class Builder {
94         private int mGranularity;
95         private RectF mStartArea;
96         private RectF mEndArea;
97         private String mFallbackText;
98 
99         /**
100          * Define text selection granularity. Intersecting words/characters will be
101          * included in the operation.
102          * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or
103          * {@link HandwritingGesture#GRANULARITY_CHARACTER}.
104          * @return {@link Builder}.
105          */
106         @NonNull
107         @SuppressLint("MissingGetterMatchingBuilder")
setGranularity(@andwritingGesture.Granularity int granularity)108         public Builder setGranularity(@HandwritingGesture.Granularity int granularity) {
109             mGranularity = granularity;
110             return this;
111         }
112 
113         /**
114          * Set rectangular single/multiline start of text selection area intersecting with text.
115          *
116          * The resulting selection is performed from the start of first word/character in the start
117          * rectangle to the end of the last word/character in the end rectangle
118          * {@link #setSelectionEndArea(RectF)}.
119          * <br/>
120          * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting
121          * /select_range_gesture_rects.png"
122          * height="300" alt="Selection strategy using two rectangles"/>
123          *  <br/>
124          *
125          * Intersection is determined using
126          * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
127          * all the words with their width/height center included in the selection rectangle.
128          * @param startArea {@link RectF} (in screen coordinates) for start of selection. This
129          * rectangle belongs to first line where selection should start.
130          */
131         @NonNull
132         @SuppressLint("MissingGetterMatchingBuilder")
setSelectionStartArea(@onNull RectF startArea)133         public Builder setSelectionStartArea(@NonNull RectF startArea) {
134             mStartArea = startArea;
135             return this;
136         }
137 
138         /**
139          * Set rectangular single/multiline end of text selection area intersecting with text.
140          *
141          * The resulting selection is performed from the start of first word/character in the start
142          * rectangle {@link #setSelectionStartArea(RectF)} to the end of the last word/character in
143          * the end rectangle.
144          * <br/>
145          * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting
146          * /select_range_gesture_rects.png"
147          * height="300" alt="Selection strategy using two rectangles"/>
148          *  <br/>
149          *
150          * The selection includes the first word/character in the rectangle, the last
151          * word/character in the rectangle, and everything in between even if it's not in the
152          * rectangle.
153          *
154          * Intersection is determined using
155          * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
156          * all the words with their width/height center included in the selection rectangle.
157          * @param endArea {@link RectF} (in screen coordinates) for start of selection. This
158          * rectangle belongs to the last line where selection should end.
159          */
160         @NonNull
161         @SuppressLint("MissingGetterMatchingBuilder")
setSelectionEndArea(@onNull RectF endArea)162         public Builder setSelectionEndArea(@NonNull RectF endArea) {
163             mEndArea = endArea;
164             return this;
165         }
166 
167         /**
168          * Set fallback text that will be committed at current cursor position if there is no
169          * applicable text beneath the area of gesture.
170          * @param fallbackText text to set
171          */
172         @NonNull
setFallbackText(@ullable String fallbackText)173         public Builder setFallbackText(@Nullable String fallbackText) {
174             mFallbackText = fallbackText;
175             return this;
176         }
177 
178         /**
179          * @return {@link SelectRangeGesture} using parameters in this
180          * {@link SelectRangeGesture.Builder}.
181          * @throws IllegalArgumentException if one or more positional parameters are not specified.
182          */
183         @NonNull
build()184         public SelectRangeGesture build() {
185             if (mStartArea == null || mStartArea.isEmpty() || mEndArea == null
186                     || mEndArea.isEmpty()) {
187                 throw new IllegalArgumentException("Selection area must be set.");
188             }
189             if (mGranularity <= GRANULARITY_UNDEFINED) {
190                 throw new IllegalArgumentException("Selection granularity must be set.");
191             }
192             return new SelectRangeGesture(mGranularity, mStartArea, mEndArea, mFallbackText);
193         }
194     }
195 
196     /**
197      * Used to make this class parcelable.
198      */
199     @NonNull
200     public static final Parcelable.Creator<SelectRangeGesture> CREATOR =
201             new Parcelable.Creator<SelectRangeGesture>() {
202                 @Override
203                 public SelectRangeGesture createFromParcel(Parcel source) {
204                     return new SelectRangeGesture(source);
205                 }
206 
207                 @Override
208                 public SelectRangeGesture[] newArray(int size) {
209                     return new SelectRangeGesture[size];
210                 }
211             };
212 
213     @Override
hashCode()214     public int hashCode() {
215         return Objects.hash(mGranularity, mStartArea, mEndArea, mFallbackText);
216     }
217 
218     @Override
equals(Object o)219     public boolean equals(Object o) {
220         if (!(o instanceof SelectRangeGesture)) return false;
221 
222         SelectRangeGesture that = (SelectRangeGesture) o;
223 
224         if (mGranularity != that.mGranularity) return false;
225         if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
226         if (!Objects.equals(mStartArea, that.mStartArea)) return false;
227         return Objects.equals(mEndArea, that.mEndArea);
228     }
229 
230     @Override
describeContents()231     public int describeContents() {
232         return 0;
233     }
234 
235     /**
236      * Used to package this object into a {@link Parcel}.
237      *
238      * @param dest The {@link Parcel} to be written.
239      * @param flags The flags used for parceling.
240      */
241     @Override
writeToParcel(@onNull Parcel dest, int flags)242     public void writeToParcel(@NonNull Parcel dest, int flags) {
243         dest.writeString8(mFallbackText);
244         dest.writeInt(mGranularity);
245         dest.writeTypedObject(mStartArea, flags);
246         dest.writeTypedObject(mEndArea, flags);
247     }
248 }
249