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