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.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.graphics.RectF;
24 import android.inputmethodservice.InputMethodService;
25 import android.os.CancellationSignal;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.view.MotionEvent;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.List;
33 import java.util.concurrent.Executor;
34 import java.util.function.IntConsumer;
35 
36 /**
37  * Base class for stylus handwriting gestures.
38  * <p>
39  * During a stylus handwriting session, user can perform a stylus gesture operation like
40  * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an
41  * area of text. IME is responsible for listening to stylus {@link MotionEvent}s using
42  * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a
43  * gesture operation.
44  * <p>
45  * While creating gesture operations {@link SelectGesture} and {@link DeleteGesture},
46  * {@code Granularity} helps pick the correct granular level of text like word level
47  * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}.
48  *
49  * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
50  * @see InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
51  * @see InputMethodService#onStartStylusHandwriting()
52  */
53 public abstract class HandwritingGesture {
54 
HandwritingGesture()55     HandwritingGesture() {}
56 
57     static final int GRANULARITY_UNDEFINED = 0;
58 
59     /**
60      * Operate text per word basis. e.g. if selection includes width-wise center of the word,
61      * whole word is selected.
62      * <p> Strategy of operating at a granular level is maintained in the UI toolkit.
63      *     A character/word/line is included if its center is within the gesture rectangle.
64      *     e.g. if a selection {@link RectF} with {@link #GRANULARITY_WORD} includes width-wise
65      *     center of the word, it should be selected.
66      *     Similarly, text in a line should be included in the operation if rectangle includes
67      *     line height center.</p>
68      * Refer to https://www.unicode.org/reports/tr29/#Word_Boundaries for more detail on how word
69      * breaks are decided.
70      */
71     public static final int GRANULARITY_WORD = 1;
72 
73     /**
74      * Operate on text per character basis. i.e. each character is selected based on its
75      * intersection with selection rectangle.
76      * <p> Strategy of operating at a granular level is maintained in the UI toolkit.
77      *     A character/word/line is included if its center is within the gesture rectangle.
78      *     e.g. if a selection {@link RectF} with {@link #GRANULARITY_CHARACTER} includes width-wise
79      *     center of the character, it should be selected.
80      *     Similarly, text in a line should be included in the operation if rectangle includes
81      *     line height center.</p>
82      */
83     public static final int GRANULARITY_CHARACTER = 2;
84 
85     /**
86      * Granular level on which text should be operated.
87      */
88     @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD})
89     @interface Granularity {}
90 
91     /**
92      * Undefined gesture type.
93      * @hide
94      */
95     @TestApi
96     public static final int GESTURE_TYPE_NONE = 0x0000;
97 
98     /**
99      * Gesture of type {@link SelectGesture} to select an area of text.
100      * @hide
101      */
102     @TestApi
103     public static final int GESTURE_TYPE_SELECT = 0x0001;
104 
105     /**
106      * Gesture of type {@link InsertGesture} to insert text at a designated point.
107      * @hide
108      */
109     @TestApi
110     public static final int GESTURE_TYPE_INSERT = 1 << 1;
111 
112     /**
113      * Gesture of type {@link DeleteGesture} to delete an area of text.
114      * @hide
115      */
116     @TestApi
117     public static final int GESTURE_TYPE_DELETE = 1 << 2;
118 
119     /**
120      * Gesture of type {@link RemoveSpaceGesture} to remove whitespace from text.
121      * @hide
122      */
123     @TestApi
124     public static final int GESTURE_TYPE_REMOVE_SPACE = 1 << 3;
125 
126     /**
127      * Gesture of type {@link JoinOrSplitGesture} to join or split text.
128      * @hide
129      */
130     @TestApi
131     public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 1 << 4;
132 
133     /**
134      * Gesture of type {@link SelectRangeGesture} to select range of text.
135      * @hide
136      */
137     @TestApi
138     public static final int GESTURE_TYPE_SELECT_RANGE = 1 << 5;
139 
140     /**
141      * Gesture of type {@link DeleteRangeGesture} to delete range of text.
142      * @hide
143      */
144     @TestApi
145     public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6;
146 
147     /**
148      * Gesture of type {@link InsertModeGesture} to begin an insert mode at a designated point.
149      * @hide
150      */
151     @TestApi
152     public static final int GESTURE_TYPE_INSERT_MODE = 1 << 7;
153 
154     /**
155      * Type of gesture like {@link #GESTURE_TYPE_SELECT}, {@link #GESTURE_TYPE_INSERT},
156      * or {@link #GESTURE_TYPE_DELETE}.
157      */
158     @IntDef(prefix = {"GESTURE_TYPE_"}, value = {
159             GESTURE_TYPE_NONE,
160             GESTURE_TYPE_SELECT,
161             GESTURE_TYPE_SELECT_RANGE,
162             GESTURE_TYPE_INSERT,
163             GESTURE_TYPE_INSERT_MODE,
164             GESTURE_TYPE_DELETE,
165             GESTURE_TYPE_DELETE_RANGE,
166             GESTURE_TYPE_REMOVE_SPACE,
167             GESTURE_TYPE_JOIN_OR_SPLIT})
168     @Retention(RetentionPolicy.SOURCE)
169     @interface GestureType{}
170 
171     /**
172      * Flags which can be any combination of {@link #GESTURE_TYPE_SELECT},
173      * {@link #GESTURE_TYPE_INSERT}, or {@link #GESTURE_TYPE_DELETE}.
174      * {@link GestureTypeFlags} can be used by editors to declare what gestures are supported
175      *  and report them in {@link EditorInfo#setSupportedHandwritingGestures(List)}.
176      * @hide
177      */
178     @IntDef(flag = true, prefix = {"GESTURE_TYPE_"}, value = {
179             GESTURE_TYPE_SELECT,
180             GESTURE_TYPE_SELECT_RANGE,
181             GESTURE_TYPE_INSERT,
182             GESTURE_TYPE_INSERT_MODE,
183             GESTURE_TYPE_DELETE,
184             GESTURE_TYPE_DELETE_RANGE,
185             GESTURE_TYPE_REMOVE_SPACE,
186             GESTURE_TYPE_JOIN_OR_SPLIT})
187     @Retention(RetentionPolicy.SOURCE)
188     public @interface GestureTypeFlags{}
189 
190     @GestureType int mType = GESTURE_TYPE_NONE;
191 
192     /**
193      * Returns the gesture type {@link GestureType}.
194      * {@link GestureType} can be used by editors to declare what gestures are supported and report
195      * them in {@link EditorInfo#setSupportedHandwritingGestures(List)}.
196      * @hide
197      */
198     @TestApi
getGestureType()199     public final @GestureType int getGestureType() {
200         return mType;
201     }
202 
203     @Nullable
204     String mFallbackText;
205 
206     /**
207      * The fallback text that will be committed at current cursor position if there is no applicable
208      * text beneath the area of gesture.
209      * For example, select can fail if gesture is drawn over area that has no text beneath.
210      * example 2: join can fail if the gesture is drawn over text but there is no whitespace.
211      */
212     @Nullable
getFallbackText()213     public final String getFallbackText() {
214         return mFallbackText;
215     }
216 
217     /**
218      * Dump data into a byte array so that you can pass the data across process boundary.
219      *
220      * @return byte array data.
221      * @see #fromByteArray(byte[])
222      * @hide
223      */
224     @TestApi
225     @NonNull
toByteArray()226     public final byte[] toByteArray() {
227         if (!(this instanceof Parcelable)) {
228             throw new UnsupportedOperationException(getClass() + " is not Parcelable");
229         }
230         final Parcelable self = (Parcelable) this;
231         if ((self.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
232             throw new UnsupportedOperationException("Gesture that contains FD is not supported");
233         }
234         Parcel parcel = null;
235         try {
236             parcel = Parcel.obtain();
237             ParcelableHandwritingGesture.of(this).writeToParcel(parcel, 0);
238             return parcel.marshall();
239         } finally {
240             if (parcel != null) {
241                 parcel.recycle();
242             }
243         }
244     }
245 
246     /**
247      * Create a new instance from byte array obtained from {@link #toByteArray()}.
248      *
249      * @param buffer byte array obtained from {@link #toByteArray()}
250      * @return A new instance of {@link HandwritingGesture} subclass.
251      * @hide
252      */
253     @TestApi
254     @NonNull
fromByteArray(@onNull byte[] buffer)255     public static HandwritingGesture fromByteArray(@NonNull byte[] buffer) {
256         Parcel parcel = null;
257         try {
258             parcel = Parcel.obtain();
259             parcel.unmarshall(buffer, 0, buffer.length);
260             parcel.setDataPosition(0);
261             return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel).get();
262         } finally {
263             if (parcel != null) {
264                 parcel.recycle();
265             }
266         }
267     }
268 }
269