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