1 /* 2 * Copyright 2023 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.hardware.input; 18 19 import android.annotation.IntDef; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.SystemApi; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.os.SystemClock; 26 import android.view.InputEvent; 27 import android.view.MotionEvent; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 32 /** 33 * An event describing a stylus interaction originating from a remote device. 34 * 35 * The tool type, location and action are required; tilts and pressure are optional. 36 * 37 * @hide 38 */ 39 @SystemApi 40 public final class VirtualStylusMotionEvent implements Parcelable { 41 private static final int TILT_MIN = -90; 42 private static final int TILT_MAX = 90; 43 private static final int PRESSURE_MIN = 0; 44 private static final int PRESSURE_MAX = 255; 45 46 /** @hide */ 47 public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN; 48 /** Tool type indicating that a stylus is the origin of the event. */ 49 public static final int TOOL_TYPE_STYLUS = MotionEvent.TOOL_TYPE_STYLUS; 50 /** Tool type indicating that an eraser is the origin of the event. */ 51 public static final int TOOL_TYPE_ERASER = MotionEvent.TOOL_TYPE_ERASER; 52 /** @hide */ 53 @IntDef(prefix = { "TOOL_TYPE_" }, value = { 54 TOOL_TYPE_UNKNOWN, 55 TOOL_TYPE_STYLUS, 56 TOOL_TYPE_ERASER, 57 }) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface ToolType {} 60 61 /** @hide */ 62 public static final int ACTION_UNKNOWN = -1; 63 /** 64 * Action indicating the stylus has been pressed down to the screen. ACTION_DOWN with pressure 65 * {@code 0} indicates that the stylus is hovering over the screen, and non-zero pressure 66 * indicates that the stylus is touching the screen. 67 */ 68 public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN; 69 /** Action indicating the stylus has been lifted from the screen. */ 70 public static final int ACTION_UP = MotionEvent.ACTION_UP; 71 /** Action indicating the stylus has been moved along the screen. */ 72 public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE; 73 /** @hide */ 74 @IntDef(prefix = { "ACTION_" }, value = { 75 ACTION_UNKNOWN, 76 ACTION_DOWN, 77 ACTION_UP, 78 ACTION_MOVE, 79 }) 80 @Retention(RetentionPolicy.SOURCE) 81 public @interface Action {} 82 83 @ToolType 84 private final int mToolType; 85 @Action 86 private final int mAction; 87 private final int mX; 88 private final int mY; 89 private final int mPressure; 90 private final int mTiltX; 91 private final int mTiltY; 92 private final long mEventTimeNanos; 93 VirtualStylusMotionEvent(@oolType int toolType, @Action int action, int x, int y, int pressure, int tiltX, int tiltY, long eventTimeNanos)94 private VirtualStylusMotionEvent(@ToolType int toolType, @Action int action, int x, int y, 95 int pressure, int tiltX, int tiltY, long eventTimeNanos) { 96 mToolType = toolType; 97 mAction = action; 98 mX = x; 99 mY = y; 100 mPressure = pressure; 101 mTiltX = tiltX; 102 mTiltY = tiltY; 103 mEventTimeNanos = eventTimeNanos; 104 } 105 VirtualStylusMotionEvent(@onNull Parcel parcel)106 private VirtualStylusMotionEvent(@NonNull Parcel parcel) { 107 mToolType = parcel.readInt(); 108 mAction = parcel.readInt(); 109 mX = parcel.readInt(); 110 mY = parcel.readInt(); 111 mPressure = parcel.readInt(); 112 mTiltX = parcel.readInt(); 113 mTiltY = parcel.readInt(); 114 mEventTimeNanos = parcel.readLong(); 115 } 116 117 @Override writeToParcel(@onNull Parcel dest, int flags)118 public void writeToParcel(@NonNull Parcel dest, int flags) { 119 dest.writeInt(mToolType); 120 dest.writeInt(mAction); 121 dest.writeInt(mX); 122 dest.writeInt(mY); 123 dest.writeInt(mPressure); 124 dest.writeInt(mTiltX); 125 dest.writeInt(mTiltY); 126 dest.writeLong(mEventTimeNanos); 127 } 128 129 @Override describeContents()130 public int describeContents() { 131 return 0; 132 } 133 134 /** 135 * Returns the tool type associated with this event. 136 */ 137 @ToolType getToolType()138 public int getToolType() { 139 return mToolType; 140 } 141 142 /** 143 * Returns the action associated with this event. 144 */ 145 @Action getAction()146 public int getAction() { 147 return mAction; 148 } 149 150 /** 151 * Returns the x-axis location associated with this event. 152 */ getX()153 public int getX() { 154 return mX; 155 } 156 157 /** 158 * Returns the y-axis location associated with this event. 159 */ getY()160 public int getY() { 161 return mY; 162 } 163 164 /** 165 * Returns the pressure associated with this event. {@code 0} pressure indicates that the stylus 166 * is hovering, otherwise the stylus is touching the screen. Returns {@code 255} if omitted. 167 */ getPressure()168 public int getPressure() { 169 return mPressure; 170 } 171 172 /** 173 * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the 174 * y-z plane and the plane containing both the stylus axis and the y axis. A positive tiltX is 175 * to the right, in the direction of increasing x values. {@code 0} tilt indicates that the 176 * stylus is perpendicular to the x-axis. Returns {@code 0} if omitted. 177 * 178 * @see Builder#setTiltX 179 */ getTiltX()180 public int getTiltX() { 181 return mTiltX; 182 } 183 184 /** 185 * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the 186 * x-z plane and the plane containing both the stylus axis and the x axis. A positive tiltY is 187 * towards the user, in the direction of increasing y values. {@code 0} tilt indicates that the 188 * stylus is perpendicular to the y-axis. Returns {@code 0} if omitted. 189 * 190 * @see Builder#setTiltY 191 */ getTiltY()192 public int getTiltY() { 193 return mTiltY; 194 } 195 196 /** 197 * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but 198 * with nanosecond (instead of millisecond) precision. 199 * 200 * @see InputEvent#getEventTime() 201 */ getEventTimeNanos()202 public long getEventTimeNanos() { 203 return mEventTimeNanos; 204 } 205 206 /** 207 * Builder for {@link VirtualStylusMotionEvent}. 208 */ 209 public static final class Builder { 210 211 @ToolType 212 private int mToolType = TOOL_TYPE_UNKNOWN; 213 @Action 214 private int mAction = ACTION_UNKNOWN; 215 private int mX = 0; 216 private int mY = 0; 217 private boolean mIsXSet = false; 218 private boolean mIsYSet = false; 219 private int mPressure = PRESSURE_MAX; 220 private int mTiltX = 0; 221 private int mTiltY = 0; 222 private long mEventTimeNanos = 0L; 223 224 /** 225 * Creates a {@link VirtualStylusMotionEvent} object with the current builder configuration. 226 * 227 * @throws IllegalArgumentException if one of the required arguments (action, tool type, 228 * x-axis location and y-axis location) is missing. 229 * {@link VirtualStylusMotionEvent} for a detailed explanation. 230 */ 231 @NonNull build()232 public VirtualStylusMotionEvent build() { 233 if (mToolType == TOOL_TYPE_UNKNOWN) { 234 throw new IllegalArgumentException( 235 "Cannot build stylus motion event with unset tool type"); 236 } 237 if (mAction == ACTION_UNKNOWN) { 238 throw new IllegalArgumentException( 239 "Cannot build stylus motion event with unset action"); 240 } 241 if (!mIsXSet) { 242 throw new IllegalArgumentException( 243 "Cannot build stylus motion event with unset x-axis location"); 244 } 245 if (!mIsYSet) { 246 throw new IllegalArgumentException( 247 "Cannot build stylus motion event with unset y-axis location"); 248 } 249 return new VirtualStylusMotionEvent(mToolType, mAction, mX, mY, mPressure, mTiltX, 250 mTiltY, mEventTimeNanos); 251 } 252 253 /** 254 * Sets the tool type of the event. 255 * 256 * @return this builder, to allow for chaining of calls 257 */ 258 @NonNull setToolType(@oolType int toolType)259 public Builder setToolType(@ToolType int toolType) { 260 if (toolType != TOOL_TYPE_STYLUS && toolType != TOOL_TYPE_ERASER) { 261 throw new IllegalArgumentException("Unsupported stylus tool type: " + toolType); 262 } 263 mToolType = toolType; 264 return this; 265 } 266 267 /** 268 * Sets the action of the event. 269 * 270 * @return this builder, to allow for chaining of calls 271 */ 272 @NonNull setAction(@ction int action)273 public Builder setAction(@Action int action) { 274 if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE) { 275 throw new IllegalArgumentException("Unsupported stylus action : " + action); 276 } 277 mAction = action; 278 return this; 279 } 280 281 /** 282 * Sets the x-axis location of the event. 283 * 284 * @return this builder, to allow for chaining of calls 285 */ 286 @NonNull setX(int absX)287 public Builder setX(int absX) { 288 mX = absX; 289 mIsXSet = true; 290 return this; 291 } 292 293 /** 294 * Sets the y-axis location of the event. 295 * 296 * @return this builder, to allow for chaining of calls 297 */ 298 @NonNull setY(int absY)299 public Builder setY(int absY) { 300 mY = absY; 301 mIsYSet = true; 302 return this; 303 } 304 305 /** 306 * Sets the pressure of the event. {@code 0} pressure indicates that the stylus is hovering, 307 * otherwise the stylus is touching the screen. This field is optional and can be omitted 308 * (defaults to {@code 255}). 309 * 310 * @param pressure The pressure of the stylus. 311 * 312 * @throws IllegalArgumentException if the pressure is smaller than 0 or greater than 255. 313 * 314 * @return this builder, to allow for chaining of calls 315 */ 316 @NonNull setPressure( @ntRangefrom = PRESSURE_MIN, to = PRESSURE_MAX) int pressure)317 public Builder setPressure( 318 @IntRange(from = PRESSURE_MIN, to = PRESSURE_MAX) int pressure) { 319 if (pressure < PRESSURE_MIN || pressure > PRESSURE_MAX) { 320 throw new IllegalArgumentException( 321 "Pressure should be between " + PRESSURE_MIN + " and " + PRESSURE_MAX); 322 } 323 mPressure = pressure; 324 return this; 325 } 326 327 /** 328 * Sets the x-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is 329 * perpendicular to the x-axis. This field is optional and can be omitted (defaults to 330 * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation 331 * of the stylus, given by {@link MotionEvent#AXIS_TILT} and 332 * {@link MotionEvent#AXIS_ORIENTATION} respectively. 333 * 334 * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90. 335 * 336 * @return this builder, to allow for chaining of calls 337 * 338 * @see VirtualStylusMotionEvent#getTiltX 339 * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields"> 340 * Stylus tilt and orientation</a> 341 */ 342 @NonNull setTiltX(@ntRangefrom = TILT_MIN, to = TILT_MAX) int tiltX)343 public Builder setTiltX(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltX) { 344 validateTilt(tiltX); 345 mTiltX = tiltX; 346 return this; 347 } 348 349 /** 350 * Sets the y-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is 351 * perpendicular to the y-axis. This field is optional and can be omitted (defaults to 352 * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation 353 * of the stylus, given by {@link MotionEvent#AXIS_TILT} and 354 * {@link MotionEvent#AXIS_ORIENTATION} respectively. 355 * 356 * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90. 357 * 358 * @return this builder, to allow for chaining of calls 359 * 360 * @see VirtualStylusMotionEvent#getTiltY 361 * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields"> 362 * Stylus tilt and orientation</a> 363 */ 364 @NonNull setTiltY(@ntRangefrom = TILT_MIN, to = TILT_MAX) int tiltY)365 public Builder setTiltY(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltY) { 366 validateTilt(tiltY); 367 mTiltY = tiltY; 368 return this; 369 } 370 371 /** 372 * Sets the time (in nanoseconds) when this specific event was generated. This may be 373 * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of 374 * millisecond), but can be different depending on the use case. 375 * This field is optional and can be omitted. 376 * <p> 377 * If this field is unset, then the time at which this event is sent to the framework would 378 * be considered as the event time (even though 379 * {@link VirtualStylusMotionEvent#getEventTimeNanos()}) would return {@code 0L}). 380 * 381 * @return this builder, to allow for chaining of calls 382 * @see InputEvent#getEventTime() 383 */ 384 @NonNull setEventTimeNanos(long eventTimeNanos)385 public Builder setEventTimeNanos(long eventTimeNanos) { 386 if (eventTimeNanos < 0L) { 387 throw new IllegalArgumentException("Event time cannot be negative"); 388 } 389 mEventTimeNanos = eventTimeNanos; 390 return this; 391 } 392 validateTilt(int tilt)393 private void validateTilt(int tilt) { 394 if (tilt < TILT_MIN || tilt > TILT_MAX) { 395 throw new IllegalArgumentException( 396 "Tilt must be between " + TILT_MIN + " and " + TILT_MAX); 397 } 398 } 399 } 400 401 @NonNull 402 public static final Parcelable.Creator<VirtualStylusMotionEvent> CREATOR = 403 new Parcelable.Creator<>() { 404 public VirtualStylusMotionEvent createFromParcel(Parcel source) { 405 return new VirtualStylusMotionEvent(source); 406 } 407 public VirtualStylusMotionEvent[] newArray(int size) { 408 return new VirtualStylusMotionEvent[size]; 409 } 410 }; 411 } 412