Skip to content

Commit f808a6a

Browse files
Added long press to click detection (#1741)
* Added long press to click detection * Apply suggestions from code review Co-authored-by: Jamie Lynch <fractalwrench@gmail.com> * Suggested changes made: Gesture is now a sealed class * Made suggested changes --------- Co-authored-by: Jamie Lynch <fractalwrench@gmail.com>
1 parent 4d949c1 commit f808a6a

4 files changed

Lines changed: 375 additions & 16 deletions

File tree

instrumentation/view-click/src/main/kotlin/io/opentelemetry/android/instrumentation/view/click/ViewClickEventGenerator.kt

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import android.view.View
1111
import android.view.ViewGroup
1212
import android.view.Window
1313
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_CLICK_EVENT_NAME
14-
import io.opentelemetry.android.instrumentation.view.click.internal.HARDWARE_POINTER_BUTTON
15-
import io.opentelemetry.android.instrumentation.view.click.internal.HARDWARE_POINTER_CLICKS
16-
import io.opentelemetry.android.instrumentation.view.click.internal.HARDWARE_POINTER_TYPE
17-
import io.opentelemetry.android.instrumentation.view.click.internal.TapEvent
14+
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_LONG_PRESS_EVENT_NAME
15+
import io.opentelemetry.android.instrumentation.view.click.internal.Gesture
1816
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_CLICK_EVENT_NAME
17+
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_LONG_PRESS_EVENT_NAME
1918
import io.opentelemetry.api.common.Attributes
2019
import io.opentelemetry.api.logs.LogRecordBuilder
2120
import io.opentelemetry.api.logs.Logger
@@ -38,15 +37,15 @@ internal class ViewClickEventGenerator(
3837
windowRef?.get()?.let { window ->
3938

4039

41-
val tapEvent = TapEvent(motionEvent)
40+
val gesture = Gesture.Click(motionEvent, 2)
4241

43-
createEvent(APP_SCREEN_CLICK_EVENT_NAME, tapEvent, 2)
42+
createEvent(APP_SCREEN_CLICK_EVENT_NAME, gesture)
4443
.setAttribute(APP_SCREEN_COORDINATE_Y, motionEvent.y.toLong())
4544
.setAttribute(APP_SCREEN_COORDINATE_X, motionEvent.x.toLong())
4645
.emit()
4746

4847
findTargetForTap(window.decorView, motionEvent.x, motionEvent.y)?.let { view ->
49-
createEvent(VIEW_CLICK_EVENT_NAME, tapEvent, 2)
48+
createEvent(VIEW_CLICK_EVENT_NAME, gesture)
5049
.setAllAttributes(createViewAttributes(view))
5150
.emit()
5251
}
@@ -59,21 +58,38 @@ internal class ViewClickEventGenerator(
5958
windowRef?.get()?.let { window ->
6059

6160

62-
val tapEvent = TapEvent(motionEvent)
61+
val gesture = Gesture.Click(motionEvent, 1)
6362

64-
createEvent(APP_SCREEN_CLICK_EVENT_NAME, tapEvent, 1)
63+
createEvent(APP_SCREEN_CLICK_EVENT_NAME, gesture)
6564
.setAttribute(APP_SCREEN_COORDINATE_Y, motionEvent.y.toLong())
6665
.setAttribute(APP_SCREEN_COORDINATE_X, motionEvent.x.toLong())
6766
.emit()
6867

6968
findTargetForTap(window.decorView, motionEvent.x, motionEvent.y)?.let { view ->
70-
createEvent(VIEW_CLICK_EVENT_NAME, tapEvent, 1)
69+
createEvent(VIEW_CLICK_EVENT_NAME, gesture)
7170
.setAllAttributes(createViewAttributes(view))
7271
.emit()
7372
}
7473
}
7574
return false
7675
}
76+
77+
override fun onLongPress(e: MotionEvent) {
78+
windowRef?.get()?.let { window ->
79+
80+
val gesture = Gesture.LongPress(e)
81+
createEvent(APP_SCREEN_LONG_PRESS_EVENT_NAME, gesture)
82+
.setAttribute(APP_SCREEN_COORDINATE_Y, e.y.toLong())
83+
.setAttribute(APP_SCREEN_COORDINATE_X, e.x.toLong())
84+
.emit()
85+
86+
findTargetForTap(window.decorView, e.x, e.y)?.let { view ->
87+
createEvent(VIEW_LONG_PRESS_EVENT_NAME, gesture)
88+
.setAllAttributes(createViewAttributes(view))
89+
.emit()
90+
}
91+
}
92+
}
7793
}
7894

7995
val gestureDetector = GestureDetector(null, gestureListener)
@@ -101,13 +117,16 @@ internal class ViewClickEventGenerator(
101117
windowRef = null
102118
}
103119

104-
private fun createEvent(name: String, tapEvent: TapEvent, clicks: Int): LogRecordBuilder =
105-
eventLogger
120+
private fun createEvent(name: String, gesture: Gesture): LogRecordBuilder {
121+
122+
val logger = eventLogger
106123
.logRecordBuilder()
107124
.setEventName(name)
108-
.setAttribute(HARDWARE_POINTER_CLICKS, clicks)
109-
.setAttribute(HARDWARE_POINTER_TYPE, tapEvent.toolTypeDescription)
110-
.setAttribute(HARDWARE_POINTER_BUTTON, tapEvent.buttonStateDescription)
125+
.setAllAttributes(gesture.attributes)
126+
127+
return logger
128+
}
129+
111130

112131
private fun createViewAttributes(view: View): Attributes {
113132
val builder = Attributes.builder()

instrumentation/view-click/src/main/kotlin/io/opentelemetry/android/instrumentation/view/click/internal/ViewUtils.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
package io.opentelemetry.android.instrumentation.view.click.internal
77

88
import android.view.MotionEvent
9+
import io.opentelemetry.api.common.Attributes
910

1011
internal const val APP_SCREEN_CLICK_EVENT_NAME = "app.screen.click"
1112
internal const val VIEW_CLICK_EVENT_NAME = "app.widget.click"
13+
internal const val APP_SCREEN_LONG_PRESS_EVENT_NAME = "app.screen.longpress"
14+
internal const val VIEW_LONG_PRESS_EVENT_NAME = "app.widget.longpress"
1215

1316
internal const val HARDWARE_POINTER_TYPE = "hw.pointer.type"
1417

@@ -37,7 +40,7 @@ internal fun toolTypeToString(toolTypeInt: Int): String {
3740
}
3841
}
3942

40-
internal class TapEvent(
43+
internal class TapEventMetadata(
4144
private val motionEvent: MotionEvent
4245
) {
4346

@@ -58,3 +61,21 @@ internal class TapEvent(
5861
}
5962

6063
}
64+
65+
internal sealed class Gesture(val m: MotionEvent) {
66+
private val t = TapEventMetadata(m)
67+
var attributes = Attributes.empty()
68+
69+
init {
70+
attributes = attributes.toBuilder().put(HARDWARE_POINTER_TYPE, t.toolTypeDescription).build()
71+
if(t.buttonStateDescription != null) {
72+
attributes = attributes.toBuilder().put(HARDWARE_POINTER_BUTTON, t.buttonStateDescription).build()
73+
}
74+
}
75+
class LongPress(val motionEvent: MotionEvent): Gesture(motionEvent)
76+
class Click(val motionEvent: MotionEvent, clicks: Int): Gesture(motionEvent) {
77+
init {
78+
attributes = attributes.toBuilder().put(HARDWARE_POINTER_CLICKS, clicks.toLong()).build()
79+
}
80+
}
81+
}

instrumentation/view-click/src/test/kotlin/TestUtils.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,49 @@ fun getSingleTapSequence(x: Float, y: Float, toolType: Int = MotionEvent.TOOL_TY
161161
)
162162
}
163163

164+
fun getLongPressSequence(x: Float, y: Float, toolType: Int = MotionEvent.TOOL_TYPE_FINGER, buttonState: Int = 0,
165+
stayDownLongEnough: Boolean = true)
166+
: Array<MotionEvent> {
167+
require(toolType in allowedToolTypes) {
168+
"Invalid tool type"
169+
}
170+
171+
if (buttonState != 0) {
172+
require(toolType == MotionEvent.TOOL_TYPE_MOUSE || toolType == MotionEvent.TOOL_TYPE_STYLUS) {
173+
"Invalid tool type for button state"
174+
}
175+
require(buttonState in allowedButtonStates) { "Invalid button state" }
176+
}
177+
178+
val initialTime = SystemClock.uptimeMillis()
179+
180+
val pointerProperties = MotionEvent.PointerProperties()
181+
pointerProperties.id = 0
182+
pointerProperties.toolType = toolType
183+
184+
val pointerCoords = PointerCoordsBuilder.newBuilder().setCoords(x, y).build()
185+
val delay = if (stayDownLongEnough) {
186+
ViewConfiguration.getLongPressTimeout()
187+
} else {
188+
100
189+
}
190+
return arrayOf(
191+
MotionEvent.obtain(initialTime, initialTime,
192+
MotionEvent.ACTION_DOWN, 1, arrayOf(pointerProperties),
193+
arrayOf(pointerCoords), 0, buttonState, 1f, 1f,
194+
0, 0, 0, 0),
195+
MotionEvent.obtain(initialTime, initialTime + delay,
196+
MotionEvent.ACTION_UP, 1, arrayOf(pointerProperties),
197+
arrayOf(pointerCoords), 0, buttonState, 1f, 1f,
198+
0, 0, 0, 0)
199+
)
200+
}
201+
164202
fun fastForwardDoubleTapTimeout() {
165203
ShadowLooper.idleMainLooper(ViewConfiguration.getDoubleTapTimeout().toLong(), TimeUnit.MILLISECONDS)
166204
}
205+
206+
fun fastForwardLongPressTimeout() {
207+
val allowanceTime = 150L
208+
ShadowLooper.idleMainLooper(ViewConfiguration.getLongPressTimeout().toLong() + allowanceTime, TimeUnit.MILLISECONDS)
209+
}

0 commit comments

Comments
 (0)