11package io .sentry .android .core ;
22
3+ import android .app .Activity ;
34import android .app .AlertDialog ;
5+ import android .app .Application ;
46import android .content .Context ;
7+ import android .content .ContextWrapper ;
58import android .os .Bundle ;
69import android .view .View ;
710import android .widget .Button ;
1821import io .sentry .protocol .Feedback ;
1922import io .sentry .protocol .SentryId ;
2023import io .sentry .protocol .User ;
24+ import java .lang .ref .WeakReference ;
2125import org .jetbrains .annotations .NotNull ;
2226import org .jetbrains .annotations .Nullable ;
2327
@@ -28,8 +32,10 @@ public class SentryUserFeedbackForm extends AlertDialog {
2832 private final @ Nullable SentryId associatedEventId ;
2933 private @ Nullable OnDismissListener delegate ;
3034
31- private final @ Nullable OptionsConfiguration configuration ;
32- private final @ Nullable SentryFeedbackOptions .OptionsConfigurator configurator ;
35+ private final @ NotNull SentryFeedbackOptions resolvedFeedbackOptions ;
36+
37+ private @ Nullable SentryShakeDetector shakeDetector ;
38+ private @ Nullable Application .ActivityLifecycleCallbacks shakeLifecycleCallbacks ;
3339
3440 SentryUserFeedbackForm (
3541 final @ NotNull Context context ,
@@ -39,9 +45,118 @@ public class SentryUserFeedbackForm extends AlertDialog {
3945 final @ Nullable SentryFeedbackOptions .OptionsConfigurator configurator ) {
4046 super (context , themeResId );
4147 this .associatedEventId = associatedEventId ;
42- this .configuration = configuration ;
43- this .configurator = configurator ;
48+ this .resolvedFeedbackOptions =
49+ new SentryFeedbackOptions (Sentry .getCurrentScopes ().getOptions ().getFeedbackOptions ());
50+ if (configuration != null ) {
51+ configuration .configure (context , resolvedFeedbackOptions );
52+ }
53+ if (configurator != null ) {
54+ configurator .configure (resolvedFeedbackOptions );
55+ }
4456 SentryIntegrationPackageStorage .getInstance ().addIntegration ("UserFeedbackWidget" );
57+ maybeStartShakeDetection (context );
58+ }
59+
60+ private void maybeStartShakeDetection (final @ NotNull Context context ) {
61+ final @ NotNull SentryFeedbackOptions globalFeedbackOptions =
62+ Sentry .getCurrentScopes ().getOptions ().getFeedbackOptions ();
63+ if (!resolvedFeedbackOptions .isUseShakeGesture () || globalFeedbackOptions .isUseShakeGesture ()) {
64+ return ;
65+ }
66+ final @ Nullable Activity activity = getActivity (context );
67+ if (activity == null ) {
68+ return ;
69+ }
70+ final @ NotNull SentryOptions options = Sentry .getCurrentScopes ().getOptions ();
71+ shakeDetector = new SentryShakeDetector (options .getLogger ());
72+ final @ NotNull WeakReference <Activity > activityRef = new WeakReference <>(activity );
73+ shakeDetector .start (activity , () -> {
74+ final @ Nullable Activity active = activityRef .get ();
75+ if (active != null && !active .isFinishing () && !active .isDestroyed ()) {
76+ active .runOnUiThread (() -> {
77+ if (!active .isFinishing () && !active .isDestroyed ()) {
78+ show ();
79+ }
80+ });
81+ }
82+ });
83+ final @ NotNull Application app = activity .getApplication ();
84+ shakeLifecycleCallbacks = new ShakeLifecycleCallbacks (activityRef );
85+ app .registerActivityLifecycleCallbacks (shakeLifecycleCallbacks );
86+ }
87+
88+ private void stopShakeDetection () {
89+ if (shakeDetector != null ) {
90+ shakeDetector .close ();
91+ shakeDetector = null ;
92+ }
93+ if (shakeLifecycleCallbacks != null ) {
94+ final @ Nullable Activity activity = getActivity (getContext ());
95+ if (activity != null ) {
96+ activity .getApplication ().unregisterActivityLifecycleCallbacks (shakeLifecycleCallbacks );
97+ }
98+ shakeLifecycleCallbacks = null ;
99+ }
100+ }
101+
102+ private static @ Nullable Activity getActivity (final @ NotNull Context context ) {
103+ Context current = context ;
104+ while (current instanceof ContextWrapper ) {
105+ if (current instanceof Activity ) {
106+ return (Activity ) current ;
107+ }
108+ current = ((ContextWrapper ) current ).getBaseContext ();
109+ }
110+ return null ;
111+ }
112+
113+ private class ShakeLifecycleCallbacks implements Application .ActivityLifecycleCallbacks {
114+ private final @ NotNull WeakReference <Activity > activityRef ;
115+
116+ ShakeLifecycleCallbacks (final @ NotNull WeakReference <Activity > activityRef ) {
117+ this .activityRef = activityRef ;
118+ }
119+
120+ @ Override
121+ public void onActivityResumed (final @ NotNull Activity activity ) {
122+ if (activity == activityRef .get () && shakeDetector != null ) {
123+ shakeDetector .start (activity , () -> {
124+ final @ Nullable Activity active = activityRef .get ();
125+ if (active != null && !active .isFinishing () && !active .isDestroyed ()) {
126+ active .runOnUiThread (() -> {
127+ if (!active .isFinishing () && !active .isDestroyed ()) {
128+ show ();
129+ }
130+ });
131+ }
132+ });
133+ }
134+ }
135+
136+ @ Override
137+ public void onActivityPaused (final @ NotNull Activity activity ) {
138+ if (activity == activityRef .get () && shakeDetector != null ) {
139+ shakeDetector .stop ();
140+ }
141+ }
142+
143+ @ Override
144+ public void onActivityDestroyed (final @ NotNull Activity activity ) {
145+ if (activity == activityRef .get ()) {
146+ stopShakeDetection ();
147+ }
148+ }
149+
150+ @ Override
151+ public void onActivityCreated (
152+ final @ NotNull Activity activity , final @ Nullable Bundle savedInstanceState ) {}
153+ @ Override
154+ public void onActivityStarted (final @ NotNull Activity activity ) {}
155+ @ Override
156+ public void onActivityStopped (final @ NotNull Activity activity ) {}
157+ @ Override
158+ public void onActivitySaveInstanceState (
159+ final @ NotNull Activity activity , final @ NotNull Bundle outState ) {}
45160 }
46161
47162 @ Override
@@ -57,14 +172,7 @@ protected void onCreate(Bundle savedInstanceState) {
57172 setContentView (R .layout .sentry_dialog_user_feedback );
58173 setCancelable (isCancelable );
59174
60- final @ NotNull SentryFeedbackOptions feedbackOptions =
61- new SentryFeedbackOptions (Sentry .getCurrentScopes ().getOptions ().getFeedbackOptions ());
62- if (configuration != null ) {
63- configuration .configure (getContext (), feedbackOptions );
64- }
65- if (configurator != null ) {
66- configurator .configure (feedbackOptions );
67- }
175+ final @ NotNull SentryFeedbackOptions feedbackOptions = resolvedFeedbackOptions ;
68176 final @ NotNull TextView lblTitle = findViewById (R .id .sentry_dialog_user_feedback_title );
69177 final @ NotNull ImageView imgLogo = findViewById (R .id .sentry_dialog_user_feedback_logo );
70178 final @ NotNull TextView lblName = findViewById (R .id .sentry_dialog_user_feedback_txt_name );
0 commit comments