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 ✨
Internal Changes 🔧Deps
🤖 This preview updates automatically when you update the PR. |
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| f064536 | 327.04 ms | 405.35 ms | 78.31 ms |
| cf708bd | 408.35 ms | 458.98 ms | 50.63 ms |
| d15471f | 294.13 ms | 399.49 ms | 105.36 ms |
| 9fbb112 | 361.43 ms | 427.57 ms | 66.14 ms |
| 2387c2c | 317.04 ms | 354.60 ms | 37.56 ms |
| 806307f | 357.85 ms | 424.64 ms | 66.79 ms |
| b193867 | 331.08 ms | 397.06 ms | 65.98 ms |
| dc4cc7a | 361.10 ms | 439.53 ms | 78.43 ms |
| 694d587 | 379.62 ms | 400.80 ms | 21.18 ms |
| 17a0955 | 372.53 ms | 446.70 ms | 74.17 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| f064536 | 1.58 MiB | 2.20 MiB | 633.90 KiB |
| cf708bd | 1.58 MiB | 2.11 MiB | 539.71 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| 9fbb112 | 1.58 MiB | 2.11 MiB | 539.18 KiB |
| 2387c2c | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| 806307f | 1.58 MiB | 2.10 MiB | 533.42 KiB |
| b193867 | 1.58 MiB | 2.19 MiB | 620.00 KiB |
| dc4cc7a | 1.58 MiB | 2.19 MiB | 619.28 KiB |
| 694d587 | 1.58 MiB | 2.19 MiB | 620.06 KiB |
| 17a0955 | 1.58 MiB | 2.10 MiB | 533.20 KiB |
Previous results on branch: antonis/feedback-shake
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 395b61e | 318.16 ms | 373.86 ms | 55.70 ms |
| 2052d5f | 331.52 ms | 401.71 ms | 70.19 ms |
| ba57364 | 317.45 ms | 360.35 ms | 42.90 ms |
| 3a14925 | 327.15 ms | 380.52 ms | 53.37 ms |
| 90c01e9 | 314.38 ms | 370.51 ms | 56.13 ms |
| a1a1e57 | 323.92 ms | 367.22 ms | 43.31 ms |
| 58a9026 | 330.31 ms | 370.45 ms | 40.13 ms |
| 090d0b1 | 331.70 ms | 407.46 ms | 75.75 ms |
| d743634 | 404.69 ms | 507.35 ms | 102.66 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 395b61e | 1.58 MiB | 2.29 MiB | 727.32 KiB |
| 2052d5f | 1.58 MiB | 2.29 MiB | 726.85 KiB |
| ba57364 | 1.58 MiB | 2.29 MiB | 727.15 KiB |
| 3a14925 | 1.58 MiB | 2.29 MiB | 727.39 KiB |
| 90c01e9 | 1.58 MiB | 2.29 MiB | 727.37 KiB |
| a1a1e57 | 1.58 MiB | 2.29 MiB | 727.28 KiB |
| 58a9026 | 1.58 MiB | 2.29 MiB | 727.33 KiB |
| 090d0b1 | 0 B | 0 B | 0 B |
| d743634 | 0 B | 0 B | 0 B |
- 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
Outdated
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>
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/AndroidOptionsInitializer.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/SentryShakeDetector.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/SentryShakeDetector.java
Outdated
Show resolved
Hide resolved
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
The detector was constructed with NoOpLogger and the logger field was final, so all diagnostic messages (sensor unavailable warnings) were silently swallowed. Now init(context, logger) updates the logger from SentryOptions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sentry Build Distribution
|
…java into antonis/feedback-shake
Thank you for the feedback @romtsn 🙇
Good idea 👍 Added with 65122ca |
Sentry Build Distribution
|
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/SentryShakeDetector.java
Show resolved
Hide resolved
- Clear currentActivity in onActivityDestroyed to prevent holding a stale reference to a destroyed activity context - Reset shakeCount and firstShakeTimestamp in stop() to prevent cross-session false triggers across pause/resume cycles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/SentryShakeDetector.java
Show resolved
Hide resolved
Sentry Build Distribution
|
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
When a dialog is showing on Activity A and the user navigates to Activity B (e.g. via notification), onActivityResumed(B) overwrites currentActivity. Later onActivityDestroyed(A) can't match and cleanup never runs, leaving isDialogShowing permanently stuck. Now we detect this in onActivityResumed and clean up proactively. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Outdated
Show resolved
Hide resolved
Sentry Build Distribution
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 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
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/SentryShakeDetector.java
Show resolved
Hide resolved
The onFormClose lambda was reading previousOnFormClose field at dismiss time. If onActivityResumed or onActivityDestroyed already restored and nulled the field, the lambda would overwrite onFormClose with null. Now captured as a local variable at dialog creation time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sentry Build Distribution
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 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/SentryShakeDetector.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/FeedbackShakeIntegration.java
Show resolved
Hide resolved
When close() is called while a dialog is showing, lifecycle callbacks are unregistered so onActivityDestroyed cleanup won't fire. Restore previousOnFormClose and reset dialog state in close() to prevent the callback from being permanently overwritten. 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/SentryShakeDetector.java
Show resolved
Hide resolved
Sentry Build Distribution
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 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
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 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/SentryShakeDetector.java
Show resolved
Hide resolved
Add proactive activity validity check inside the runOnUiThread lambda to avoid hitting the catch block with a BadTokenException when the activity becomes invalid between the shake callback and UI execution. Co-Authored-By: Claude Opus 4.6 (1M context) <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.

📜 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.