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,127 @@ 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 (
74+ activity ,
75+ () -> {
76+ final @ Nullable Activity active = activityRef .get ();
77+ if (active != null && !active .isFinishing () && !active .isDestroyed ()) {
78+ active .runOnUiThread (
79+ () -> {
80+ if (!active .isFinishing () && !active .isDestroyed ()) {
81+ show ();
82+ }
83+ });
84+ }
85+ });
86+ final @ NotNull Application app = activity .getApplication ();
87+ shakeLifecycleCallbacks = new ShakeLifecycleCallbacks (activityRef );
88+ app .registerActivityLifecycleCallbacks (shakeLifecycleCallbacks );
89+ }
90+
91+ private void stopShakeDetection () {
92+ if (shakeDetector != null ) {
93+ shakeDetector .close ();
94+ shakeDetector = null ;
95+ }
96+ if (shakeLifecycleCallbacks != null ) {
97+ final @ Nullable Activity activity = getActivity (getContext ());
98+ if (activity != null ) {
99+ activity .getApplication ().unregisterActivityLifecycleCallbacks (shakeLifecycleCallbacks );
100+ }
101+ shakeLifecycleCallbacks = null ;
102+ }
103+ }
104+
105+ private static @ Nullable Activity getActivity (final @ NotNull Context context ) {
106+ Context current = context ;
107+ while (current instanceof ContextWrapper ) {
108+ if (current instanceof Activity ) {
109+ return (Activity ) current ;
110+ }
111+ current = ((ContextWrapper ) current ).getBaseContext ();
112+ }
113+ return null ;
114+ }
115+
116+ private class ShakeLifecycleCallbacks implements Application .ActivityLifecycleCallbacks {
117+ private final @ NotNull WeakReference <Activity > activityRef ;
118+
119+ ShakeLifecycleCallbacks (final @ NotNull WeakReference <Activity > activityRef ) {
120+ this .activityRef = activityRef ;
121+ }
122+
123+ @ Override
124+ public void onActivityResumed (final @ NotNull Activity activity ) {
125+ if (activity == activityRef .get () && shakeDetector != null ) {
126+ shakeDetector .start (
127+ activity ,
128+ () -> {
129+ final @ Nullable Activity active = activityRef .get ();
130+ if (active != null && !active .isFinishing () && !active .isDestroyed ()) {
131+ active .runOnUiThread (
132+ () -> {
133+ if (!active .isFinishing () && !active .isDestroyed ()) {
134+ show ();
135+ }
136+ });
137+ }
138+ });
139+ }
140+ }
141+
142+ @ Override
143+ public void onActivityPaused (final @ NotNull Activity activity ) {
144+ if (activity == activityRef .get () && shakeDetector != null ) {
145+ shakeDetector .stop ();
146+ }
147+ }
148+
149+ @ Override
150+ public void onActivityDestroyed (final @ NotNull Activity activity ) {
151+ if (activity == activityRef .get ()) {
152+ stopShakeDetection ();
153+ }
154+ }
155+
156+ @ Override
157+ public void onActivityCreated (
158+ final @ NotNull Activity activity , final @ Nullable Bundle savedInstanceState ) {}
159+
160+ @ Override
161+ public void onActivityStarted (final @ NotNull Activity activity ) {}
162+
163+ @ Override
164+ public void onActivityStopped (final @ NotNull Activity activity ) {}
165+
166+ @ Override
167+ public void onActivitySaveInstanceState (
168+ final @ NotNull Activity activity , final @ NotNull Bundle outState ) {}
45169 }
46170
47171 @ Override
@@ -57,14 +181,7 @@ protected void onCreate(Bundle savedInstanceState) {
57181 setContentView (R .layout .sentry_dialog_user_feedback );
58182 setCancelable (isCancelable );
59183
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- }
184+ final @ NotNull SentryFeedbackOptions feedbackOptions = resolvedFeedbackOptions ;
68185 final @ NotNull TextView lblTitle = findViewById (R .id .sentry_dialog_user_feedback_title );
69186 final @ NotNull ImageView imgLogo = findViewById (R .id .sentry_dialog_user_feedback_logo );
70187 final @ NotNull TextView lblName = findViewById (R .id .sentry_dialog_user_feedback_txt_name );
0 commit comments