Skip to content

Added basic double-tap, based off view click by cleverchuk. Addresses #1642 and #1576#1681

Open
DavidGrath wants to merge 8 commits intoopen-telemetry:mainfrom
DavidGrath:issue-1642-double-tap
Open

Added basic double-tap, based off view click by cleverchuk. Addresses #1642 and #1576#1681
DavidGrath wants to merge 8 commits intoopen-telemetry:mainfrom
DavidGrath:issue-1642-double-tap

Conversation

@DavidGrath
Copy link
Copy Markdown

The core difference between this and the original View Click is the use of a GestureDetector instead of relying on MotionEvent directly. As brought up in my comments on the issue, I'd like to know if it's desirable to add configurations to selectively track the double-tap event per-view via some form of allowlist. Then there's the issue of interplay between this and the original view.click

@DavidGrath DavidGrath requested a review from a team as a code owner April 9, 2026 20:39
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Apr 9, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@DavidGrath
Copy link
Copy Markdown
Author

Okay, the steps I took before opening this PR include running spotlessApply and testDebugUnitTest successfully. After fixing the missing proguard file and detekt errors, please I'd like to know if there's anything else I would need to do to ensure a successful set of checks

@DavidGrath
Copy link
Copy Markdown
Author

I'll make the relevant fixes and revert within 48 hours

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 11, 2026

Codecov Report

❌ Patch coverage is 77.19298% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.31%. Comparing base (7168bd3) to head (75c1407).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...d/instrumentation/view/click/internal/ViewUtils.kt 38.46% 6 Missing and 2 partials ⚠️
...trumentation/view/click/ViewClickEventGenerator.kt 88.63% 0 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1681      +/-   ##
==========================================
+ Coverage   62.09%   62.31%   +0.21%     
==========================================
  Files         159      160       +1     
  Lines        3411     3460      +49     
  Branches      345      355      +10     
==========================================
+ Hits         2118     2156      +38     
- Misses       1199     1205       +6     
- Partials       94       99       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +3 to +4
internal const val APP_SCREEN_DOUBLE_TAP_EVENT_NAME = "app.screen.doubletap"
internal const val VIEW_DOUBLE_TAP_EVENT_NAME = "app.widget.doubletap"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use a generic click/tap event name and use attribute to ie distinguish between the type ie single/double click

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.

The idea is to eventually expand to other gestures including scroll and pinch, that's the reasoning behind me leaving single tap distinct from double tap

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no problem with scroll & pinch having dedicated name as they would also have different attributes. However in the case of clicks (single vs double) they would have the same attributes and the number of definitions triples when you consider that it could also be a left vs right click when using a mouse.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see it working out like this:

  • Add the first source and class from InputDevice.getSources() source as 2 attributes
  • Add MotionEvent.getToolType() as an attribute
  • Add MotionEvent.getButtonState() as an attribute if it's greater than 0
  • Merge what I've done so far into the original view click and delete the new double tap folder

I'd be translating the returned integers into strings

What do you think?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and also add click.type for either single or double

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that as otherwise we would have had so many different event names.

The question is attribute names. I am thinking:

  • hw.pointer.type for the tool type as it is describing the hardware that was used
  • hw.pointer.button for the button state with stylus_primary shortened to primary etc.
  • hw.pointer.clicks for how many clicks ie 1 or 2

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I assume we're only focusing on SOURCE_CLASS_POINTER since that's where gestures typically come from. I think it's good, although I'd prefer hardware in full instead of shortened.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try and get back to this on or before the 18th

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, note hw.* is a defined namespace in semconv. 😉

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Thanks for pointing that out. Noted

@DavidGrath
Copy link
Copy Markdown
Author

Main points of discussion:

  • If the button state isn't primary, then should the event still be sent or actively suppressed?
  • I've changed the original code to indirectly rely on ViewConfiguration.getDoubleTapTimeout() through the use of onSingleTapConfirmed. Is this an okay change or should I rely on onSingleTapUp instead?

@DavidGrath
Copy link
Copy Markdown
Author

@thompson-tomo , please how do the changes look?

Comment on lines +51 to +61
val buttonStateInt = motionEvent.buttonState
val toolTypeHasButtons = toolTypeInt == MotionEvent.TOOL_TYPE_MOUSE || toolTypeInt == MotionEvent.TOOL_TYPE_STYLUS
val isButtonPrimary = buttonStateInt == MotionEvent.BUTTON_PRIMARY || buttonStateInt == MotionEvent.BUTTON_STYLUS_PRIMARY
if(toolTypeHasButtons && !isButtonPrimary) {
return false
}
val buttonState = buttonStateToString(buttonStateInt)

if(buttonState != null) {
appEvent.setAttribute(HARDWARE_POINTER_BUTTON, buttonState)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we need to restrict it to primary given we already have an attribute to indicate which button.

Suggested change
val buttonStateInt = motionEvent.buttonState
val toolTypeHasButtons = toolTypeInt == MotionEvent.TOOL_TYPE_MOUSE || toolTypeInt == MotionEvent.TOOL_TYPE_STYLUS
val isButtonPrimary = buttonStateInt == MotionEvent.BUTTON_PRIMARY || buttonStateInt == MotionEvent.BUTTON_STYLUS_PRIMARY
if(toolTypeHasButtons && !isButtonPrimary) {
return false
}
val buttonState = buttonStateToString(buttonStateInt)
if(buttonState != null) {
appEvent.setAttribute(HARDWARE_POINTER_BUTTON, buttonState)
}
val toolTypeHasButtons = toolTypeInt == MotionEvent.TOOL_TYPE_MOUSE || toolTypeInt == MotionEvent.TOOL_TYPE_STYLUS
val buttonState = null
if(toolTypeHasButtons) {
val buttonStateInt = motionEvent.buttonState
buttonState = buttonStateToString(buttonStateInt)
}
if(buttonState != null) {
appEvent.setAttribute(HARDWARE_POINTER_BUTTON, buttonState)
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll need to delete the corresponding test I wrote for this as well

Copy link
Copy Markdown
Contributor

@thompson-tomo thompson-tomo Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, note the change should be replicated to single click.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.logRecordBuilder()
.setEventName(name)

private fun createViewAttributes(view: View): Attributes {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also be adding the hw.pointer.* attributes on her as well?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might not be necessary, since app.screen.click is always sent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it already has the same attributes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true but I was thinking of the use case of where the collector is filtering events and might filter out app.click hence the view click event should be self sufficient.

Copy link
Copy Markdown
Contributor

@cleverchuk cleverchuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May need to standardize the new attributes in semconv otherwise LGTM!

.setAttribute(HARDWARE_POINTER_CLICKS, 2)
.setAttribute(HARDWARE_POINTER_TYPE, toolType)

val toolTypeHasButtons = toolTypeInt == MotionEvent.TOOL_TYPE_MOUSE || toolTypeInt == MotionEvent.TOOL_TYPE_STYLUS
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider driving this and other information via an enum in ViewUtils as that will reduce the amount of logic in this class. Here's a non-exhaustive example of what I mean:

enum class TapEvent(
    val motionEvent: Int,
    val toolType: Int,
) {


    val toolTypeDescription: String // derive from MotionEvent.TOOL_TYPE_*
    val hasButtons: Boolean = toolTypeInt == MotionEvent.TOOL_TYPE_MOUSE || toolTypeInt == MotionEvent.TOOL_TYPE_STYLUS
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I've applied the other suggestions. I'll look into this and give my feedback by Wednesday or Thursday

Co-authored-by: Jamie Lynch <fractalwrench@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants