1 /*
<lambda>null2  * Copyright 2020 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 androidx.compose.ui.input.pointer
18 
19 import android.os.Build
20 import android.view.KeyEvent
21 import android.view.MotionEvent
22 import android.view.MotionEvent.ACTION_SCROLL
23 import android.view.MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
24 import android.view.MotionEvent.CLASSIFICATION_DEEP_PRESS
25 import android.view.MotionEvent.CLASSIFICATION_NONE
26 import android.view.MotionEvent.CLASSIFICATION_PINCH
27 import android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
28 import androidx.annotation.IntDef
29 import androidx.collection.LongSparseArray
30 import androidx.compose.ui.util.fastForEach
31 
32 internal actual typealias NativePointerButtons = Int
33 
34 internal actual typealias NativePointerKeyboardModifiers = Int
35 
36 /**
37  * Restricts Ints to `MotionEvent`'s classification types. See the
38  * [Android documentation on MotionEvent.getClassification()]
39  * (https://developer.android.com/reference/android/view/MotionEvent#getClassification()) for more
40  * details.
41  */
42 @IntDef(
43     CLASSIFICATION_NONE,
44     CLASSIFICATION_AMBIGUOUS_GESTURE,
45     CLASSIFICATION_DEEP_PRESS,
46     CLASSIFICATION_TWO_FINGER_SWIPE,
47     CLASSIFICATION_PINCH
48 )
49 @Retention(AnnotationRetention.SOURCE) // Only for compile-time checks
50 internal annotation class MotionEventClassification
51 
52 /** Describes a pointer input change event that has occurred at a particular point in time. */
53 actual class PointerEvent
54 internal actual constructor(
55     /** The changes. */
56     actual val changes: List<PointerInputChange>,
57     internal val internalPointerEvent: InternalPointerEvent?
58 ) {
59     /**
60      * The underlying Android [MotionEvent] that triggered this [PointerEvent].
61      *
62      * This property provides access to the raw [MotionEvent] for retrieving platform-specific
63      * information not yet exposed by the Compose [PointerEvent] API (e.g., stylus tilt angle).
64      *
65      * **Important Considerations:**
66      * 1. **Read-Only:** The returned [MotionEvent] is strictly read-only. Modifying it will lead to
67      *    unpredictable behavior.
68      * 2. **Transient:** Do not store a reference to this [MotionEvent]. The Android framework may
69      *    recycle it, rendering its state undefined and causing errors if accessed later. Access the
70      *    data only within the scope where the [PointerEvent] is received.
71      * 3. **Metadata Only:** This [MotionEvent] should *not* be used for primary input handling
72      *    logic (e.g., determining pointer position or button presses). Rely on the properties of
73      *    [PointerEvent] and [PointerInputChange] for this purpose. The [MotionEvent] is intended
74      *    solely for accessing supplemental metadata.
75      * 4. **Nullability:** This property will be `null` in two cases:
76      *     * The [PointerEvent] was fabricated within Compose (i.e., not directly from a system
77      *       input event).
78      *     * The [PointerEvent] has already been dispatched within the Compose input system. (See
79      *       [androidx.compose.ui.samples.PointerEventMotionEventSample] for details).
80      *
81      * @sample androidx.compose.ui.samples.PointerEventMotionEventSample
82      */
83     val motionEvent: MotionEvent?
84         get() = internalPointerEvent?.motionEvent
85 
86     /**
87      * Returns
88      * [`MotionEvent`'s classification](https://developer.android.com/reference/android/view/MotionEvent#getClassification()).
89      */
90     @get:MotionEventClassification
91     val classification: Int =
92         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
93             motionEvent?.classification ?: CLASSIFICATION_NONE
94         } else {
95             CLASSIFICATION_NONE // Return NONE for versions lower than Android Q
96         }
97 
98     /** @param changes The changes. */
99     actual constructor(changes: List<PointerInputChange>) : this(changes, null)
100 
101     actual val buttons = PointerButtons(motionEvent?.buttonState ?: 0)
102 
103     actual val keyboardModifiers = PointerKeyboardModifiers(motionEvent?.metaState ?: 0)
104 
105     actual var type: PointerEventType = calculatePointerEventType()
106         internal set
107 
108     private fun calculatePointerEventType(): PointerEventType {
109         val motionEvent = motionEvent
110         if (motionEvent != null) {
111             return when (motionEvent.actionMasked) {
112                 MotionEvent.ACTION_DOWN,
113                 MotionEvent.ACTION_POINTER_DOWN -> PointerEventType.Press
114                 MotionEvent.ACTION_UP,
115                 MotionEvent.ACTION_POINTER_UP -> PointerEventType.Release
116                 MotionEvent.ACTION_HOVER_MOVE,
117                 MotionEvent.ACTION_MOVE -> PointerEventType.Move
118                 MotionEvent.ACTION_HOVER_ENTER -> PointerEventType.Enter
119                 MotionEvent.ACTION_HOVER_EXIT -> PointerEventType.Exit
120                 ACTION_SCROLL -> PointerEventType.Scroll
121                 else -> PointerEventType.Unknown
122             }
123         }
124         // Used for testing.
125         changes.fastForEach {
126             if (it.changedToUpIgnoreConsumed()) {
127                 return PointerEventType.Release
128             }
129             if (it.changedToDownIgnoreConsumed()) {
130                 return PointerEventType.Press
131             }
132         }
133         return PointerEventType.Move
134     }
135 
136     // only because PointerEvent was a data class
137     fun component1(): List<PointerInputChange> = changes
138 
139     // only because PointerEvent was a data class
140     fun copy(changes: List<PointerInputChange>, motionEvent: MotionEvent?): PointerEvent =
141         when (motionEvent) {
142             null -> PointerEvent(changes, null)
143             this.motionEvent -> PointerEvent(changes, internalPointerEvent)
144             else -> {
145                 val changesArray = LongSparseArray<PointerInputChange>(changes.size)
146                 val pointerEventData = ArrayList<PointerInputEventData>(changes.size)
147                 changes.fastForEach { change ->
148                     changesArray.put(change.id.value, change)
149                     pointerEventData +=
150                         PointerInputEventData(
151                             change.id,
152                             change.uptimeMillis,
153                             change.position,
154                             change.position,
155                             change.pressed,
156                             change.pressure,
157                             change.type,
158                             this.internalPointerEvent?.activeHoverEvent(change.id) == true
159                         )
160                 }
161 
162                 val pointerInputEvent =
163                     PointerInputEvent(motionEvent.eventTime, pointerEventData, motionEvent)
164                 val event = InternalPointerEvent(changesArray, pointerInputEvent)
165                 PointerEvent(changes, event)
166             }
167         }
168 }
169 
EmptyPointerKeyboardModifiersnull170 internal actual fun EmptyPointerKeyboardModifiers() = PointerKeyboardModifiers(0)
171 
172 actual val PointerButtons.isPrimaryPressed: Boolean
173     get() = packedValue and (MotionEvent.BUTTON_PRIMARY or MotionEvent.BUTTON_STYLUS_PRIMARY) != 0
174 
175 actual val PointerButtons.isSecondaryPressed: Boolean
176     get() =
177         packedValue and (MotionEvent.BUTTON_SECONDARY or MotionEvent.BUTTON_STYLUS_SECONDARY) != 0
178 
179 actual val PointerButtons.isTertiaryPressed: Boolean
180     get() = packedValue and MotionEvent.BUTTON_TERTIARY != 0
181 
182 actual val PointerButtons.isBackPressed: Boolean
183     get() = packedValue and MotionEvent.BUTTON_BACK != 0
184 
185 actual val PointerButtons.isForwardPressed: Boolean
186     get() = packedValue and MotionEvent.BUTTON_FORWARD != 0
187 
188 actual fun PointerButtons.isPressed(buttonIndex: Int): Boolean =
189     when (buttonIndex) {
190         0 -> isPrimaryPressed
191         1 -> isSecondaryPressed
192         2,
193         3,
194         4 -> packedValue and (1 shl buttonIndex) != 0
195         else -> packedValue and (1 shl (buttonIndex + 2)) != 0
196     }
197 
198 actual val PointerButtons.areAnyPressed: Boolean
199     get() = packedValue != 0
200 
indexOfFirstPressednull201 actual fun PointerButtons.indexOfFirstPressed(): Int {
202     if (packedValue == 0) {
203         return -1
204     }
205     var index = 0
206     // shift stylus primary and secondary to primary and secondary
207     var shifted = ((packedValue and 0x60) ushr 5) or (packedValue and 0x60.inv())
208     while (shifted and 1 == 0) {
209         index++
210         shifted = shifted ushr 1
211     }
212     return index
213 }
214 
indexOfLastPressednull215 actual fun PointerButtons.indexOfLastPressed(): Int {
216     // shift stylus primary and secondary to primary and secondary
217     var shifted = ((packedValue and 0x60) ushr 5) or (packedValue and 0x60.inv())
218     var index = -1
219     while (shifted != 0) {
220         index++
221         shifted = shifted ushr 1
222     }
223     return index
224 }
225 
226 actual val PointerKeyboardModifiers.isCtrlPressed: Boolean
227     get() = (packedValue and KeyEvent.META_CTRL_ON) != 0
228 
229 actual val PointerKeyboardModifiers.isMetaPressed: Boolean
230     get() = (packedValue and KeyEvent.META_META_ON) != 0
231 
232 actual val PointerKeyboardModifiers.isAltPressed: Boolean
233     get() = (packedValue and KeyEvent.META_ALT_ON) != 0
234 
235 actual val PointerKeyboardModifiers.isAltGraphPressed: Boolean
236     get() = false
237 
238 actual val PointerKeyboardModifiers.isSymPressed: Boolean
239     get() = (packedValue and KeyEvent.META_SYM_ON) != 0
240 
241 actual val PointerKeyboardModifiers.isShiftPressed: Boolean
242     get() = (packedValue and KeyEvent.META_SHIFT_ON) != 0
243 
244 actual val PointerKeyboardModifiers.isFunctionPressed: Boolean
245     get() = (packedValue and KeyEvent.META_FUNCTION_ON) != 0
246 
247 actual val PointerKeyboardModifiers.isCapsLockOn: Boolean
248     get() = (packedValue and KeyEvent.META_CAPS_LOCK_ON) != 0
249 
250 actual val PointerKeyboardModifiers.isScrollLockOn: Boolean
251     get() = (packedValue and KeyEvent.META_SCROLL_LOCK_ON) != 0
252 
253 actual val PointerKeyboardModifiers.isNumLockOn: Boolean
254     get() = (packedValue and KeyEvent.META_NUM_LOCK_ON) != 0
255