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 deleting 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 deletion of text in
33  * toolkit widgets like {@link TextView}.
34  * <p>Note: this deletes text within a range <em>between</em> two given areas. To delete all text
35  * <em>within</em> a single area, use {@link DeleteGesture}.</p>
36  */
37 public final class DeleteRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
38 
39     private @Granularity int mGranularity;
40     private RectF mStartArea;
41     private RectF mEndArea;
42 
DeleteRangeGesture( int granularity, RectF startArea, RectF endArea, String fallbackText)43     private DeleteRangeGesture(
44             int granularity, RectF startArea, RectF endArea, String fallbackText) {
45         mType = GESTURE_TYPE_DELETE_RANGE;
46         mStartArea = startArea;
47         mEndArea = endArea;
48         mGranularity = granularity;
49         mFallbackText = fallbackText;
50     }
51 
DeleteRangeGesture(@onNull Parcel source)52     private DeleteRangeGesture(@NonNull Parcel source) {
53         mType = GESTURE_TYPE_DELETE_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 Deletion start area {@link RectF} in screen coordinates.
72      *
73      * Getter for deletion area set with {@link Builder#setDeletionStartArea(RectF)}.
74      */
75     @NonNull
getDeletionStartArea()76     public RectF getDeletionStartArea() {
77         return mStartArea;
78     }
79 
80     /**
81      * Returns the Deletion end area {@link RectF} in screen coordinates.
82      *
83      * Getter for deletion area set with {@link Builder#setDeletionEndArea(RectF)}.
84      */
85     @NonNull
getDeletionEndArea()86     public RectF getDeletionEndArea() {
87         return mEndArea;
88     }
89 
90     /**
91      * Builder for {@link DeleteRangeGesture}. 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 deletion 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(@ranularity int granularity)108         public Builder setGranularity(@Granularity int granularity) {
109             mGranularity = granularity;
110             return this;
111         }
112 
113         /**
114          * Set rectangular single/multiline start of text deletion area intersecting with text.
115          *
116          * The resulting deletion 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 #setDeletionEndArea(RectF)}.
119          * <br/>
120          * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting
121          * /delete_range_gesture_rects.png"
122          * height="300" alt="Deletion 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 deletion rectangle.
128          * @param startArea {@link RectF} (in screen coordinates) for start of deletion. This
129          * rectangle belongs to first line where deletion should start.
130          */
131         @NonNull
132         @SuppressLint("MissingGetterMatchingBuilder")
setDeletionStartArea(@onNull RectF startArea)133         public Builder setDeletionStartArea(@NonNull RectF startArea) {
134             mStartArea = startArea;
135             return this;
136         }
137 
138         /**
139          * Set rectangular single/multiline end of text deletion area intersecting with text.
140          *
141          * The resulting deletion is performed from the start of first word/character in the start
142          * rectangle {@link #setDeletionStartArea(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          * /delete_range_gesture_rects.png"
147          * height="300" alt="Deletion strategy using two rectangles"/>
148          *
149          * Intersection is determined using
150          * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
151          * all the words with their width/height center included in the deletion rectangle.
152          * @param endArea {@link RectF} (in screen coordinates) for start of deletion. This
153          * rectangle belongs to the last line where deletion should end.
154          */
155         @NonNull
156         @SuppressLint("MissingGetterMatchingBuilder")
setDeletionEndArea(@onNull RectF endArea)157         public Builder setDeletionEndArea(@NonNull RectF endArea) {
158             mEndArea = endArea;
159             return this;
160         }
161 
162         /**
163          * Set fallback text that will be committed at current cursor position if there is no
164          * applicable text beneath the area of gesture.
165          * @param fallbackText text to set
166          */
167         @NonNull
setFallbackText(@ullable String fallbackText)168         public Builder setFallbackText(@Nullable String fallbackText) {
169             mFallbackText = fallbackText;
170             return this;
171         }
172 
173         /**
174          * @return {@link DeleteRangeGesture} using parameters in this
175          * {@link DeleteRangeGesture.Builder}.
176          * @throws IllegalArgumentException if one or more positional parameters are not specified.
177          */
178         @NonNull
build()179         public DeleteRangeGesture build() {
180             if (mStartArea == null || mStartArea.isEmpty() || mEndArea == null
181                     || mEndArea.isEmpty()) {
182                 throw new IllegalArgumentException("Deletion area must be set.");
183             }
184             if (mGranularity <= GRANULARITY_UNDEFINED) {
185                 throw new IllegalArgumentException("Deletion granularity must be set.");
186             }
187             return new DeleteRangeGesture(mGranularity, mStartArea, mEndArea, mFallbackText);
188         }
189     }
190 
191     /**
192      * Used to make this class parcelable.
193      */
194     @NonNull
195     public static final Creator<DeleteRangeGesture> CREATOR =
196             new Creator<DeleteRangeGesture>() {
197                 @Override
198                 public DeleteRangeGesture createFromParcel(Parcel source) {
199                     return new DeleteRangeGesture(source);
200                 }
201 
202                 @Override
203                 public DeleteRangeGesture[] newArray(int size) {
204                     return new DeleteRangeGesture[size];
205                 }
206             };
207 
208     @Override
hashCode()209     public int hashCode() {
210         return Objects.hash(mGranularity, mStartArea, mEndArea, mFallbackText);
211     }
212 
213     @Override
equals(Object o)214     public boolean equals(Object o) {
215         if (!(o instanceof DeleteRangeGesture)) return false;
216 
217         DeleteRangeGesture that = (DeleteRangeGesture) o;
218 
219         if (mGranularity != that.mGranularity) return false;
220         if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
221         if (!Objects.equals(mStartArea, that.mStartArea)) return false;
222         return Objects.equals(mEndArea, that.mEndArea);
223     }
224 
225     @Override
describeContents()226     public int describeContents() {
227         return 0;
228     }
229 
230     /**
231      * Used to package this object into a {@link Parcel}.
232      *
233      * @param dest The {@link Parcel} to be written.
234      * @param flags The flags used for parceling.
235      */
236     @Override
writeToParcel(@onNull Parcel dest, int flags)237     public void writeToParcel(@NonNull Parcel dest, int flags) {
238         dest.writeString8(mFallbackText);
239         dest.writeInt(mGranularity);
240         dest.writeTypedObject(mStartArea, flags);
241         dest.writeTypedObject(mEndArea, flags);
242     }
243 }
244