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