feat(feedback): implement shake gesture detection#5150
feat(feedback): implement shake gesture detection#5150
Conversation
Adds SentryShakeDetector (accelerometer-based) and ShakeDetectionIntegration that shows the feedback dialog when a shake is detected. Controlled by SentryFeedbackOptions.useShakeGesture (default false). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
🤖 This preview updates automatically when you update the PR. |
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ee747ae | 374.71 ms | 455.18 ms | 80.47 ms |
| 22f4345 | 314.79 ms | 375.02 ms | 60.23 ms |
| 91bb874 | 311.00 ms | 363.47 ms | 52.47 ms |
| dba088c | 328.51 ms | 423.79 ms | 95.28 ms |
| 5b66efd | 308.67 ms | 363.85 ms | 55.18 ms |
| 96eeafa | 361.43 ms | 455.07 ms | 93.63 ms |
| 2124a46 | 319.19 ms | 415.04 ms | 95.85 ms |
| d15471f | 379.40 ms | 470.76 ms | 91.36 ms |
| bbc35bb | 324.88 ms | 425.73 ms | 100.85 ms |
| d15471f | 304.55 ms | 408.43 ms | 103.87 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
| 22f4345 | 1.58 MiB | 2.29 MiB | 719.83 KiB |
| 91bb874 | 1.58 MiB | 2.13 MiB | 559.07 KiB |
| dba088c | 1.58 MiB | 2.13 MiB | 558.99 KiB |
| 5b66efd | 1.58 MiB | 2.13 MiB | 559.07 KiB |
| 96eeafa | 1.58 MiB | 2.19 MiB | 620.21 KiB |
| 2124a46 | 1.58 MiB | 2.12 MiB | 551.51 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| bbc35bb | 1.58 MiB | 2.12 MiB | 553.01 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
Previous results on branch: antonis/feedback-shake
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 90c01e9 | 314.38 ms | 370.51 ms | 56.13 ms |
| ba57364 | 317.45 ms | 360.35 ms | 42.90 ms |
| a1a1e57 | 323.92 ms | 367.22 ms | 43.31 ms |
| 2052d5f | 331.52 ms | 401.71 ms | 70.19 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 90c01e9 | 1.58 MiB | 2.29 MiB | 727.37 KiB |
| ba57364 | 1.58 MiB | 2.29 MiB | 727.15 KiB |
| a1a1e57 | 1.58 MiB | 2.29 MiB | 727.28 KiB |
| 2052d5f | 1.58 MiB | 2.29 MiB | 726.85 KiB |
- Add volatile/AtomicLong for thread-safe cross-thread field access - Use SystemClock.elapsedRealtime() instead of System.currentTimeMillis() - Use SENSOR_DELAY_NORMAL for better battery efficiency - Add multi-shake counting (2+ threshold crossings within 1.5s window) - Handle deferred init for already-resumed activities - Wrap showDialog() in try-catch to prevent app crashes - Improve activity transition handling in onActivityPaused - Mark SentryShakeDetector as @ApiStatus.Internal - Add unit tests for SentryShakeDetector and ShakeDetectionIntegration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
… shakes Track dialog visibility with an isDialogShowing flag that is set before showing and cleared via the onFormClose callback when the dialog is dismissed. Double-checked on both sensor and UI threads to avoid races. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/ShakeDetectionIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
… growth Save the user's original onFormClose once during register() and restore it after each dialog dismiss, instead of wrapping it with a new lambda each time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
…ck flag If showDialog silently fails (e.g. activity destroyed between post and execution), isDialogShowing would stay true forever, permanently disabling shake-to-feedback. Reset it in onActivityPaused since the dialog cannot outlive its host activity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
…ActivityDestroyed AlertDialog survives pause/resume cycles (e.g. screen off/on), so resetting isDialogShowing in onActivityPaused allowed duplicate dialogs. Move the reset to onActivityDestroyed where the dialog truly cannot survive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
…back on error - Only reset isDialogShowing in onActivityDestroyed when it's the activity that hosts the dialog, not any unrelated activity. - Restore originalOnFormClose in the catch block when showDialog throws. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
| /** | ||
| * Detects shake gestures and shows the user feedback dialog when a shake is detected. Only active |
There was a problem hiding this comment.
I know the end goal is to add this to improve user feedback, but the way it is, it's doing nothing related to user feedback.
Might be a good idea to rename this to FeedbackShakeIntegration so the name of it self explains the usage of it, instead of leaving it generic, what do you think?
| (Application) context, buildInfoProvider, activityFramesTracker)); | ||
| options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context)); | ||
| options.addIntegration(new UserInteractionIntegration((Application) context, loadClass)); | ||
| options.addIntegration(new ShakeDetectionIntegration((Application) context)); |
There was a problem hiding this comment.
Q: is it possible to only add when useShakeGesture is set to true?
There was a problem hiding this comment.
At the time installDefaultIntegrations runs in AndroidOptionsInitializer, the user's configuration callback hasn't executed yet and useShakeGesture is still false. That's why register() checks the option later. This is the same pattern used by other integrations (e.g., UserInteractionIntegration is always added but checks its options in register())
sentry-android-core/src/main/java/io/sentry/android/core/SentryShakeDetector.java
Outdated
Show resolved
Hide resolved
| sensorManager.unregisterListener(this); | ||
| sensorManager = null; | ||
| } | ||
| listener = null; |
There was a problem hiding this comment.
q: why not set listener null first?
Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Outdated
Show resolved
Hide resolved
- Rename ShakeDetectionIntegration to FeedbackShakeIntegration to clarify its purpose is feedback-specific (#1) - Avoid Math.sqrt by comparing squared gForce values (#3) - Null out listener before unregistering sensor to prevent in-flight callbacks during stop (#4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
...ry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java
Outdated
Show resolved
Hide resolved
Capture the current onFormClose callback just before showing the dialog rather than caching it during register(). This ensures callbacks set by the user after SDK init are preserved across shake-triggered dialogs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the dialog's host activity is destroyed and onDismiss doesn't fire, onActivityDestroyed now restores the previous onFormClose callback on global options, preventing a stale wrapper from affecting subsequent non-shake feedback dialogs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📜 Description
Implements shake gesture detection for the user feedback form. When
useShakeGestureis enabled inSentryFeedbackOptions, shaking the device opens the feedback form.The implementation uses the device's accelerometer (via
SensorManager) with a 2.7G threshold and 1-second cooldown. A newShakeDetectionIntegrationregisters lifecycle callbacks to start/stop the detector when the activity resumes/pauses.Note: this is exposed to be used on React Native too getsentry/sentry-react-native#5754
💡 Motivation and Context
The
useShakeGestureproperty already existed inSentryFeedbackOptionsbut was never wired up. This PR implements the feature. The implementation is placed here (sentry-java) rather than in each SDK separately so it can be reused by all SDKs that embed the feedback UI (React Native, Flutter, .NET MAUI, Unity).No special permissions are required —
TYPE_ACCELEROMETERis not a protected sensor. OnlyBODY_SENSORSrequires a permission.💚 How did you test it?
SentryShakeDetectorandShakeDetectionIntegration📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
sentry-react-native will remove its own
RNSentryShakeDetectorand delegate to this class instead.