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