Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import android.view.View
import android.view.ViewGroup
import android.view.Window
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_CLICK_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_FLING_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_LONG_PRESS_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.APP_SCREEN_SCROLL_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.Gesture
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_CLICK_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_FLING_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_LONG_PRESS_EVENT_NAME
import io.opentelemetry.android.instrumentation.view.click.internal.VIEW_SCROLL_EVENT_NAME
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.logs.LogRecordBuilder
import io.opentelemetry.api.logs.Logger
Expand All @@ -38,59 +42,110 @@ internal class ViewClickEventGenerator(
override fun onDoubleTap(motionEvent: MotionEvent): Boolean {
windowRef?.get()?.let { window ->


val safeEvent = MotionEvent.obtain(motionEvent)
Comment thread
breedx-splk marked this conversation as resolved.
val gesture = Gesture.Click(motionEvent, 2)

createEvent(APP_SCREEN_CLICK_EVENT_NAME, gesture)
.setAttribute(APP_SCREEN_COORDINATE_Y, motionEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, motionEvent.x.toLong())
.setAttribute(APP_SCREEN_COORDINATE_Y, safeEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, safeEvent.x.toLong())
.emit()

findTargetForTap(window.decorView, motionEvent.x, motionEvent.y)?.let { view ->
findTargetForTap(window.decorView, safeEvent.x, safeEvent.y)?.let { view ->
createEvent(VIEW_CLICK_EVENT_NAME, gesture)
.setAllAttributes(createViewAttributes(view))
.emit()
}

safeEvent.recycle()

}
return false
}

override fun onSingleTapConfirmed(motionEvent: MotionEvent): Boolean {
windowRef?.get()?.let { window ->


val gesture = Gesture.Click(motionEvent, 1)
val safeEvent = MotionEvent.obtain(motionEvent)
val gesture = Gesture.Click(safeEvent, 1)

createEvent(APP_SCREEN_CLICK_EVENT_NAME, gesture)
.setAttribute(APP_SCREEN_COORDINATE_Y, motionEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, motionEvent.x.toLong())
.setAttribute(APP_SCREEN_COORDINATE_Y, safeEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, safeEvent.x.toLong())
.emit()

findTargetForTap(window.decorView, motionEvent.x, motionEvent.y)?.let { view ->
findTargetForTap(window.decorView, safeEvent.x, safeEvent.y)?.let { view ->
createEvent(VIEW_CLICK_EVENT_NAME, gesture)
.setAllAttributes(createViewAttributes(view))
.emit()
}
safeEvent.recycle()
}
return false
}

override fun onLongPress(e: MotionEvent) {
windowRef?.get()?.let { window ->

val gesture = Gesture.LongPress(e)
val safeEvent = MotionEvent.obtain(e)
val gesture = Gesture.LongPress(safeEvent)
createEvent(APP_SCREEN_LONG_PRESS_EVENT_NAME, gesture)
.setAttribute(APP_SCREEN_COORDINATE_Y, e.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, e.x.toLong())
.setAttribute(APP_SCREEN_COORDINATE_Y, safeEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, safeEvent.x.toLong())
.emit()

findTargetForTap(window.decorView, e.x, e.y)?.let { view ->
findTargetForTap(window.decorView, safeEvent.x, safeEvent.y)?.let { view ->
createEvent(VIEW_LONG_PRESS_EVENT_NAME, gesture)
.setAllAttributes(createViewAttributes(view))
.emit()
}
safeEvent.recycle()
}
}

override fun onScroll(
e1: MotionEvent?, e2: MotionEvent,
distanceX: Float, distanceY: Float
): Boolean {
windowRef?.get()?.let { window ->

val safeEvent = MotionEvent.obtain(e2)
val gesture = Gesture.Scroll(safeEvent, distanceX.toDouble(), distanceY.toDouble())
createEvent(APP_SCREEN_SCROLL_EVENT_NAME, gesture)
.setAttribute(APP_SCREEN_COORDINATE_Y, safeEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, safeEvent.x.toLong())
.emit()

findTargetForTap(window.decorView, safeEvent.x, safeEvent.y)?.let { view ->
createEvent(VIEW_SCROLL_EVENT_NAME, gesture)
.setAllAttributes(createViewAttributes(view))
.emit()
}
safeEvent.recycle()
}
return false
}

override fun onFling(
e1: MotionEvent?, e2: MotionEvent,
velocityX: Float, velocityY: Float
): Boolean {
windowRef?.get()?.let { window ->

val safeEvent = MotionEvent.obtain(e2)
val gesture = Gesture.Fling(safeEvent, velocityX.toDouble(), velocityY.toDouble())
createEvent(APP_SCREEN_FLING_EVENT_NAME, gesture)
.setAttribute(APP_SCREEN_COORDINATE_Y, safeEvent.y.toLong())
.setAttribute(APP_SCREEN_COORDINATE_X, safeEvent.x.toLong())
.emit()

findTargetForTap(window.decorView, safeEvent.x, safeEvent.y)?.let { view ->
createEvent(VIEW_FLING_EVENT_NAME, gesture)
.setAllAttributes(createViewAttributes(view))
.emit()
}
safeEvent.recycle()
}
return false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ internal const val HARDWARE_POINTER_BUTTON = "hw.pointer.button"

internal const val HARDWARE_POINTER_CLICKS = "hw.pointer.clicks"

internal const val APP_SCREEN_SCROLL_EVENT_NAME = "app.screen.scroll"

internal const val VIEW_SCROLL_EVENT_NAME = "app.widget.scroll"

internal const val APP_SCREEN_FLING_EVENT_NAME = "app.screen.fling"

internal const val VIEW_FLING_EVENT_NAME = "app.widget.fling"

internal const val HARDWARE_POINTER_VELOCITY_X = "hw.pointer.velocity.x"

internal const val HARDWARE_POINTER_VELOCITY_Y = "hw.pointer.velocity.y"


internal const val HARDWARE_POINTER_DISTANCE_X = "hw.pointer.distance.x"


internal const val HARDWARE_POINTER_DISTANCE_Y = "hw.pointer.distance.y"

internal fun buttonStateToString(buttonStateInt: Int): String? {
return when(buttonStateInt) {
MotionEvent.BUTTON_PRIMARY, MotionEvent.BUTTON_STYLUS_PRIMARY -> "primary"
Expand Down Expand Up @@ -78,4 +96,20 @@ internal sealed class Gesture(val m: MotionEvent) {
attributes = attributes.toBuilder().put(HARDWARE_POINTER_CLICKS, clicks.toLong()).build()
}
}
class Fling(val motionEvent: MotionEvent, val velocityX: Double, val velocityY: Double): Gesture(motionEvent) {
init {
attributes = attributes.toBuilder()
.put(HARDWARE_POINTER_VELOCITY_X, velocityX)
.put(HARDWARE_POINTER_VELOCITY_Y, velocityY)
.build()
}
}
class Scroll(val motionEvent: MotionEvent, val distanceX: Double, val distanceY: Double): Gesture(motionEvent) {
init {
attributes = attributes.toBuilder()
.put(HARDWARE_POINTER_DISTANCE_X, distanceX)
.put(HARDWARE_POINTER_DISTANCE_Y, distanceY)
.build()
}
}
}
58 changes: 58 additions & 0 deletions instrumentation/view-click/src/test/kotlin/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,55 @@ fun getLongPressSequence(x: Float, y: Float, toolType: Int = MotionEvent.TOOL_TY
)
}

fun getScrollSequence(x: Float, y: Float, angleDegrees: Double, distance: Int,
toolType: Int = MotionEvent.TOOL_TYPE_FINGER, buttonState: Int = 0,
timeMillis: Long = 100L, letGo: Boolean = false)
: Array<MotionEvent> {
require(toolType in allowedToolTypes) {
"Invalid tool type"
}

if(buttonState != 0) {
require(toolType == MotionEvent.TOOL_TYPE_MOUSE || toolType == MotionEvent.TOOL_TYPE_STYLUS) {
"Invalid tool type for button state"
}
require(buttonState in allowedButtonStates) { "Invalid button state" }
}

val initialTime = SystemClock.uptimeMillis()

val pointerProperties = MotionEvent.PointerProperties()
pointerProperties.id = 0
pointerProperties.toolType = toolType

val startPointerCoords = PointerCoordsBuilder.newBuilder().setCoords(x, y).build()
val (endX, endY) = getDestinationPoint(arrayOf(x, y), distance, angleDegrees)
val endPointerCoords = PointerCoordsBuilder.newBuilder().setCoords(endX, endY).build()

var array = arrayOf(
MotionEvent.obtain(initialTime, initialTime,
MotionEvent.ACTION_DOWN, 1, arrayOf(pointerProperties),
arrayOf(startPointerCoords), 0, buttonState, 1f, 1f,
0, 0, 0, 0),

MotionEvent.obtain(initialTime, initialTime + timeMillis,
MotionEvent.ACTION_MOVE, 1, arrayOf(pointerProperties),
arrayOf(endPointerCoords), 0, buttonState, 1f, 1f,
0, 0, 0, 0)
)
val timeToLetGoAfterMoving = 50L
if(letGo) {
array +=
MotionEvent.obtain(
initialTime, initialTime + timeMillis + timeToLetGoAfterMoving,
MotionEvent.ACTION_UP, 1, arrayOf(pointerProperties),
arrayOf(endPointerCoords), 0, buttonState, 1f, 1f,
0, 0, 0, 0
)
}
return array
}

fun fastForwardDoubleTapTimeout() {
ShadowLooper.idleMainLooper(ViewConfiguration.getDoubleTapTimeout().toLong(), TimeUnit.MILLISECONDS)
}
Expand All @@ -207,3 +256,12 @@ fun fastForwardLongPressTimeout() {
val allowanceTime = 150L
ShadowLooper.idleMainLooper(ViewConfiguration.getLongPressTimeout().toLong() + allowanceTime, TimeUnit.MILLISECONDS)
}


fun getDestinationPoint(pointA: Array<Float>, distance: Int, angleDegrees: Double): Array<Float> {
val angleRadians = angleDegrees * Math.PI / 180
val (xA, yA) = pointA
val xDist = Math.round(distance * Math.cos(angleRadians)).toInt()
val yDist = Math.round(distance * Math.sin(angleRadians)).toInt()
return arrayOf(xA + xDist, yA + yDist)
}
42 changes: 42 additions & 0 deletions instrumentation/view-click/src/test/kotlin/TestUtilsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.within
import org.junit.Test

class TestUtilsTest {

@Test
fun testGetDestinationHorizontal() {
val pointA = arrayOf(20f, 20f)
val expectedPointB = arrayOf(40f, 20f)
val (pointBx, pointBy) = getDestinationPoint(pointA, 20, 0.0)
assertThat(pointBx).isCloseTo(expectedPointB[0], within(1f))
assertThat(pointBy).isCloseTo(expectedPointB[1], within(1f))
}

@Test
fun testGetDestinationVertical() {
val pointA = arrayOf(20f, 20f)
val expectedPointB = arrayOf(20f, 40f)
val (pointBx, pointBy) = getDestinationPoint(pointA, 20, 90.0)
assertThat(pointBx).isCloseTo(expectedPointB[0], within(1f))
assertThat(pointBy).isCloseTo(expectedPointB[1], within(1f))
}

@Test
fun testGetDestinationNonCardinalAngle() {
val pointA = arrayOf(20f, 20f)
val expectedPointB = arrayOf(34.14f, 34.14f)
val (pointBx, pointBy) = getDestinationPoint(pointA, 20, 45.0)
assertThat(pointBx).isCloseTo(expectedPointB[0], within(1f))
assertThat(pointBy).isCloseTo(expectedPointB[1], within(1f))
}

@Test
fun testGetDestinationNonCardinalAngleNegativeDirection() {
val pointA = arrayOf(20f, 20f)
val expectedPointB = arrayOf(5.86f, 5.86f)
val (pointBx, pointBy) = getDestinationPoint(pointA, 20, 225.0)
assertThat(pointBx).isCloseTo(expectedPointB[0], within(1f))
assertThat(pointBy).isCloseTo(expectedPointB[1], within(1f))
}
}
Loading