@@ -27,197 +27,200 @@ import java.io.IOException
27
27
28
28
@SuppressLint(" StaticFieldLeak" ) // Reference to Activity is set to null in onActivityDestroyed
29
29
object NotificationFeedbackTrigger : Application.ActivityLifecycleCallbacks {
30
- private const val TAG : String = " NotificationFeedbackTrigger"
31
- private const val FEEBACK_NOTIFICATION_CHANNEL_ID = " InAppFeedbackNotification"
32
- private const val FEEDBACK_NOTIFICATION_ID = 1
33
- const val SCREENSHOT_FILE_NAME =
34
- " com.googletest.firebase.appdistribution.testapp.screenshot.png"
35
-
36
- private var isEnabled = false
37
- private var hasRequestedPermission = false
38
- private var currentActivity: Activity ? = null
39
- private var requestPermissionLauncher: ActivityResultLauncher <String >? = null
40
-
41
- fun initialize (application : Application ) {
42
- // Create the NotificationChannel, but only on API 26+ because
43
- // the NotificationChannel class is new and not in the support library
44
- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
45
- val channel = NotificationChannel (
46
- FEEBACK_NOTIFICATION_CHANNEL_ID ,
47
- application.getString(R .string.feedback_notification_channel_name),
48
- NotificationManager .IMPORTANCE_HIGH
49
- )
50
- channel.description =
51
- application.getString(R .string.feedback_notification_channel_description)
52
- application.getSystemService(NotificationManager ::class .java)
53
- .createNotificationChannel(channel)
54
- }
55
- application.registerActivityLifecycleCallbacks(this )
30
+ private const val TAG : String = " NotificationFeedbackTrigger"
31
+ private const val FEEBACK_NOTIFICATION_CHANNEL_ID = " InAppFeedbackNotification"
32
+ private const val FEEDBACK_NOTIFICATION_ID = 1
33
+ const val SCREENSHOT_FILE_NAME = " com.googletest.firebase.appdistribution.testapp.screenshot.png"
34
+
35
+ private var isEnabled = false
36
+ private var hasRequestedPermission = false
37
+ private var currentActivity: Activity ? = null
38
+ private var requestPermissionLauncher: ActivityResultLauncher <String >? = null
39
+
40
+ fun initialize (application : Application ) {
41
+ // Create the NotificationChannel, but only on API 26+ because
42
+ // the NotificationChannel class is new and not in the support library
43
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
44
+ val channel =
45
+ NotificationChannel (
46
+ FEEBACK_NOTIFICATION_CHANNEL_ID ,
47
+ application.getString(R .string.feedback_notification_channel_name),
48
+ NotificationManager .IMPORTANCE_LOW
49
+ )
50
+ channel.description =
51
+ application.getString(R .string.feedback_notification_channel_description)
52
+ application
53
+ .getSystemService(NotificationManager ::class .java)
54
+ .createNotificationChannel(channel)
56
55
}
57
-
58
- fun enable (currentActivity : Activity ? = null) {
59
- this .currentActivity = currentActivity
60
- isEnabled = true
61
- if (currentActivity != null ) {
62
- showNotification(currentActivity)
63
- }
56
+ application.registerActivityLifecycleCallbacks(this )
57
+ }
58
+
59
+ fun enable (currentActivity : Activity ? = null) {
60
+ this .currentActivity = currentActivity
61
+ isEnabled = true
62
+ if (currentActivity != null ) {
63
+ showNotification(currentActivity)
64
64
}
65
-
66
- fun disable () {
67
- isEnabled = false
68
- val activity = currentActivity
69
- currentActivity = null
70
- if (activity ! = null ) {
71
- cancelNotification (activity)
72
- }
65
+ }
66
+
67
+ fun disable () {
68
+ isEnabled = false
69
+ val activity = currentActivity
70
+ currentActivity = null
71
+ if (activity != null ) {
72
+ cancelNotification(activity)
73
73
}
74
-
75
- private fun showNotification (activity : Activity ) {
76
- if (ContextCompat .checkSelfPermission(activity, POST_NOTIFICATIONS ) == PERMISSION_GRANTED ) {
77
- val intent = Intent (activity, TakeScreenshotAndTriggerFeedbackActivity ::class .java)
78
- intent.addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
79
- val pendingIntent = PendingIntent .getActivity(
80
- activity, /* requestCode= */ 0 , intent, PendingIntent .FLAG_IMMUTABLE
81
- )
82
- val builder = NotificationCompat .Builder (activity, FEEBACK_NOTIFICATION_CHANNEL_ID )
83
- .setSmallIcon(R .mipmap.ic_launcher)
84
- .setContentTitle(activity.getText(R .string.feedback_notification_title))
85
- .setContentText(activity.getText(R .string.feedback_notification_text))
86
- .setPriority(NotificationCompat .PRIORITY_HIGH )
87
- .setContentIntent(pendingIntent)
88
- val notificationManager = NotificationManagerCompat .from(activity)
89
- Log .i(TAG , " Showing notification" )
90
- notificationManager.notify(FEEDBACK_NOTIFICATION_ID , builder.build())
91
- } else {
92
- // no permission to post notifications - try to get it
93
- if (hasRequestedPermission) {
94
- Log .i(
95
- TAG ,
96
- " We've already request permission. Not requesting again for the life of the activity."
97
- )
98
- } else {
99
- requestPermission(activity)
100
- }
101
- }
74
+ }
75
+
76
+ private fun showNotification (activity : Activity ) {
77
+ if (ContextCompat .checkSelfPermission(activity, POST_NOTIFICATIONS ) == PERMISSION_GRANTED ) {
78
+ val intent = Intent (activity, TakeScreenshotAndTriggerFeedbackActivity ::class .java)
79
+ intent.addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
80
+ val pendingIntent =
81
+ PendingIntent .getActivity(
82
+ activity,
83
+ /* requestCode= */ 0 ,
84
+ intent,
85
+ PendingIntent .FLAG_IMMUTABLE
86
+ )
87
+ val builder =
88
+ NotificationCompat .Builder (activity, FEEBACK_NOTIFICATION_CHANNEL_ID )
89
+ .setSmallIcon(R .mipmap.ic_launcher)
90
+ .setContentTitle(activity.getText(R .string.feedback_notification_title))
91
+ .setContentText(activity.getText(R .string.feedback_notification_text))
92
+ .setPriority(NotificationCompat .PRIORITY_LOW )
93
+ .setContentIntent(pendingIntent)
94
+ val notificationManager = NotificationManagerCompat .from(activity)
95
+ Log .i(TAG , " Showing notification" )
96
+ notificationManager.notify(FEEDBACK_NOTIFICATION_ID , builder.build())
97
+ } else {
98
+ // no permission to post notifications - try to get it
99
+ if (hasRequestedPermission) {
100
+ Log .i(
101
+ TAG ,
102
+ " We've already request permission. Not requesting again for the life of the activity."
103
+ )
104
+ } else {
105
+ requestPermission(activity)
106
+ }
102
107
}
103
-
104
- private fun requestPermission (activity : Activity ) {
105
- var launcher = requestPermissionLauncher
106
- if (launcher == null ) {
107
- Log .i(TAG , " Not requesting permission, because of inability to register for result." )
108
- } else {
109
- if (activity.shouldShowRequestPermissionRationale(POST_NOTIFICATIONS )) {
110
- Log .i(TAG , " Showing customer rationale for requesting permission." )
111
- AlertDialog .Builder (activity)
112
- .setMessage(
113
- " Using a notification to initiate feedback to the developer. "
114
- + " To enable this feature, allow the app to post notifications."
115
- )
116
- .setPositiveButton(" OK" ) { _, _ ->
117
- Log .i(TAG , " Launching request for permission." )
118
- launcher.launch(POST_NOTIFICATIONS )
119
- }
120
- .show()
121
- } else {
122
- Log .i(TAG , " Launching request for permission without rationale." )
123
- launcher.launch(POST_NOTIFICATIONS )
124
- }
125
- hasRequestedPermission = true
126
- }
127
- }
128
-
129
- private fun cancelNotification (context : Context ) {
130
- val notificationManager = NotificationManagerCompat .from(context)
131
- Log .i(TAG , " Cancelling notification" )
132
- notificationManager.cancel(FEEDBACK_NOTIFICATION_ID )
108
+ }
109
+
110
+ private fun requestPermission (activity : Activity ) {
111
+ var launcher = requestPermissionLauncher
112
+ if (launcher == null ) {
113
+ Log .i(TAG , " Not requesting permission, because of inability to register for result." )
114
+ } else {
115
+ if (activity.shouldShowRequestPermissionRationale(POST_NOTIFICATIONS )) {
116
+ Log .i(TAG , " Showing customer rationale for requesting permission." )
117
+ AlertDialog .Builder (activity)
118
+ .setMessage(
119
+ " Using a notification to initiate feedback to the developer. " +
120
+ " To enable this feature, allow the app to post notifications."
121
+ )
122
+ .setPositiveButton(" OK" ) { _, _ ->
123
+ Log .i(TAG , " Launching request for permission." )
124
+ launcher.launch(POST_NOTIFICATIONS )
125
+ }
126
+ .show()
127
+ } else {
128
+ Log .i(TAG , " Launching request for permission without rationale." )
129
+ launcher.launch(POST_NOTIFICATIONS )
130
+ }
131
+ hasRequestedPermission = true
133
132
}
134
-
135
- override fun onActivityResumed (activity : Activity ) {
136
- if (isEnabled) {
137
- if (activity !is TakeScreenshotAndTriggerFeedbackActivity ) {
138
- Log .d(TAG , " setting current activity" )
139
- currentActivity = activity
140
- }
141
- showNotification(activity)
142
- }
133
+ }
134
+
135
+ private fun cancelNotification (context : Context ) {
136
+ val notificationManager = NotificationManagerCompat .from(context)
137
+ Log .i(TAG , " Cancelling notification" )
138
+ notificationManager.cancel(FEEDBACK_NOTIFICATION_ID )
139
+ }
140
+
141
+ override fun onActivityResumed (activity : Activity ) {
142
+ if (isEnabled) {
143
+ if (activity !is TakeScreenshotAndTriggerFeedbackActivity ) {
144
+ Log .d(TAG , " setting current activity" )
145
+ currentActivity = activity
146
+ showNotification(activity)
147
+ }
143
148
}
149
+ }
144
150
145
- override fun onActivityPaused (activity : Activity ) {
146
- requestPermissionLauncher = null
147
- cancelNotification(activity)
148
- }
151
+ override fun onActivityPaused (activity : Activity ) {
152
+ cancelNotification(activity)
153
+ }
149
154
150
- override fun onActivityDestroyed (activity : Activity ) {
151
- Log .d(TAG , " clearing current activity" )
152
- currentActivity = null
155
+ override fun onActivityDestroyed (activity : Activity ) {
156
+ if (activity == currentActivity) {
157
+ Log .d(TAG , " clearing current activity" )
158
+ requestPermissionLauncher = null
159
+ currentActivity = null
153
160
}
154
-
155
- override fun onActivityCreated (activity : Activity , savedInstanceState : Bundle ? ) {
156
- if (activity is ActivityResultCaller && ! hasRequestedPermission) {
157
- requestPermissionLauncher =
158
- activity.registerForActivityResult(ActivityResultContracts .RequestPermission ()) { isGranted: Boolean ->
159
- if (! isEnabled) {
160
- Log .w(
161
- TAG ,
162
- " Trigger disabled after permission check. Abandoning notification."
163
- )
164
- } else if (isGranted) {
165
- showNotification(activity)
166
- } else {
167
- Log .i(TAG , " Permission not granted" )
168
- // TODO: Ideally we would show a message indicating the impact of not
169
- // enabling the permission, but there's no way to know if they've
170
- // permanently denied the permission, and we don't want to show them a
171
- // message after each time we try to post a notification.
172
- }
173
- }
174
- } else {
175
- Log .w(
176
- TAG ,
177
- " Not showing notification because this activity can't register for permission request results: $activity "
178
- )
161
+ }
162
+
163
+ override fun onActivityCreated (activity : Activity , savedInstanceState : Bundle ? ) {
164
+ if (activity is ActivityResultCaller && ! hasRequestedPermission) {
165
+ requestPermissionLauncher =
166
+ activity.registerForActivityResult(ActivityResultContracts .RequestPermission ()) {
167
+ isGranted: Boolean ->
168
+ if (! isEnabled) {
169
+ Log .w(TAG , " Trigger disabled after permission check. Abandoning notification." )
170
+ } else if (isGranted) {
171
+ showNotification(activity)
172
+ } else {
173
+ Log .i(TAG , " Permission not granted" )
174
+ // TODO: Ideally we would show a message indicating the impact of not
175
+ // enabling the permission, but there's no way to know if they've
176
+ // permanently denied the permission, and we don't want to show them a
177
+ // message after each time we try to post a notification.
178
+ }
179
179
}
180
+ } else {
181
+ Log .w(
182
+ TAG ,
183
+ " Not showing notification because this activity can't register for permission request results: $activity "
184
+ )
180
185
}
181
-
182
- // Other lifecycle methods
183
- override fun onActivityStarted (activity : Activity ) {}
184
- override fun onActivityStopped (activity : Activity ) {}
185
- override fun onActivitySaveInstanceState (activity : Activity , outState : Bundle ) {}
186
-
187
- fun takeScreenshot () {
188
- val activity = currentActivity
189
- if (activity != null ) {
190
- val view = activity.window.decorView.rootView
191
- val bitmap = Bitmap .createBitmap(view.width, view.height, Bitmap .Config .RGB_565 )
192
- val canvas = Canvas (bitmap)
193
- view.draw(canvas)
194
- try {
195
- activity.openFileOutput(SCREENSHOT_FILE_NAME , Context .MODE_PRIVATE )
196
- .use { outputStream ->
197
- bitmap.compress(
198
- Bitmap .CompressFormat .PNG , /* quality = */ 100 , outputStream
199
- )
200
- }
201
- Log .i(TAG , " Wrote screenshot to $SCREENSHOT_FILE_NAME " )
202
- } catch (e: IOException ) {
203
- Log .e(TAG , " Can't write $SCREENSHOT_FILE_NAME " , e)
204
- }
205
- } else {
206
- Log .e(TAG , " Can't take screenshot because current activity is unknown" )
207
- return
186
+ }
187
+
188
+ // Other lifecycle methods
189
+ override fun onActivityStarted (activity : Activity ) {}
190
+ override fun onActivityStopped (activity : Activity ) {}
191
+ override fun onActivitySaveInstanceState (activity : Activity , outState : Bundle ) {}
192
+
193
+ fun takeScreenshot () {
194
+ val activity = currentActivity
195
+ if (activity != null ) {
196
+ val view = activity.window.decorView.rootView
197
+ val bitmap = Bitmap .createBitmap(view.width, view.height, Bitmap .Config .RGB_565 )
198
+ val canvas = Canvas (bitmap)
199
+ view.draw(canvas)
200
+ try {
201
+ activity.openFileOutput(SCREENSHOT_FILE_NAME , Context .MODE_PRIVATE ).use { outputStream ->
202
+ bitmap.compress(Bitmap .CompressFormat .PNG , /* quality = */ 100 , outputStream)
208
203
}
204
+ Log .i(TAG , " Wrote screenshot to $SCREENSHOT_FILE_NAME " )
205
+ } catch (e: IOException ) {
206
+ Log .e(TAG , " Can't write $SCREENSHOT_FILE_NAME " , e)
207
+ }
208
+ } else {
209
+ Log .e(TAG , " Can't take screenshot because current activity is unknown" )
210
+ return
209
211
}
212
+ }
210
213
}
211
214
212
215
class TakeScreenshotAndTriggerFeedbackActivity : AppCompatActivity () {
213
- override fun onCreate (savedInstanceState : Bundle ? ) {
214
- super .onCreate(savedInstanceState)
215
- takeScreenshot()
216
- }
217
-
218
- override fun onResume () {
219
- super .onResume()
220
- val screenshotUri = Uri .fromFile(getFileStreamPath(SCREENSHOT_FILE_NAME ))
221
- Firebase .appDistribution.startFeedback(R .string.terms_and_conditions, screenshotUri)
222
- }
223
- }
216
+ override fun onCreate (savedInstanceState : Bundle ? ) {
217
+ super .onCreate(savedInstanceState)
218
+ takeScreenshot()
219
+ }
220
+
221
+ override fun onResume () {
222
+ super .onResume()
223
+ val screenshotUri = Uri .fromFile(getFileStreamPath(SCREENSHOT_FILE_NAME ))
224
+ Firebase .appDistribution.startFeedback(R .string.terms_and_conditions, screenshotUri)
225
+ }
226
+ }
0 commit comments