@@ -6,204 +6,112 @@ import android.app.*
6
6
import android.content.Context
7
7
import android.content.Intent
8
8
import android.content.pm.PackageManager.PERMISSION_DENIED
9
- import android.content.pm.PackageManager.PERMISSION_GRANTED
10
9
import android.graphics.Bitmap
11
10
import android.graphics.Canvas
12
11
import android.net.Uri
13
12
import android.os.Build
14
13
import android.os.Bundle
15
14
import android.util.Log
16
- import androidx.activity.result.ActivityResultCaller
17
- import androidx.activity.result.contract.ActivityResultContracts
18
- import androidx.annotation.RequiresApi
19
15
import androidx.core.app.NotificationCompat
20
16
import androidx.core.app.NotificationManagerCompat
21
17
import androidx.core.content.ContextCompat
22
18
import com.google.firebase.appdistribution.ktx.appDistribution
23
19
import com.google.firebase.ktx.Firebase
24
20
import java.io.IOException
25
21
22
+ /* *
23
+ * Shows an ongoing notification that the user can tap to take a screenshot and send feedback to the
24
+ * developer.
25
+ */
26
26
@SuppressLint(" StaticFieldLeak" ) // Reference to Activity is set to null in onActivityDestroyed
27
- object CustomNotificationFeedbackTrigger : Application.ActivityLifecycleCallbacks {
28
- private const val TAG : String = " NotificationFeedbackTrigger "
29
- private const val FEEDBACK_NOTIFICATION_CHANNEL_ID = " InAppFeedbackNotification "
27
+ object CustomNotificationFeedbackTrigger {
28
+ private const val TAG : String = " CustomNotificationFeedbackTrigger "
29
+ private const val FEEDBACK_NOTIFICATION_CHANNEL_ID = " CustomNotificationFeedbackTrigger "
30
30
private const val FEEDBACK_NOTIFICATION_ID = 1
31
31
32
- private var isEnabled = false
33
- private var hasRequestedPermission = false
32
+ var activityToScreenshot: Activity ? = null
34
33
35
- internal var activityToScreenshot: Activity ? = null
36
34
37
35
/* *
38
- * Initialize the notification trigger for this application.
36
+ * Show an ongoing notification that the user can tap to take a screenshot of the current activity
37
+ * and send feedback to the developer.
39
38
*
40
- * This should be called during [Application.onCreate].
41
- * [enable] should then be called when you want to actually show the notification.
39
+ * The passed in activity must call [cancelNotification] in its [Activity.onDestroy].
42
40
*
43
- * @param application the [Application] object
41
+ * @param activity the current activity, which will be captured by the screenshot
44
42
*/
45
- fun initialize (application : Application ) {
46
- // Create the NotificationChannel, but only on API 26+ because
47
- // the NotificationChannel class is new and not in the support library
48
- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
49
- val channel =
50
- NotificationChannel (
51
- FEEDBACK_NOTIFICATION_CHANNEL_ID ,
52
- application.getString(R .string.feedbackTriggerNotificationChannelName),
53
- NotificationManager .IMPORTANCE_HIGH
54
- )
55
- channel.description =
56
- application.getString(R .string.feedbackTriggerNotificationChannelDescription)
57
- application
58
- .getSystemService(NotificationManager ::class .java)
59
- .createNotificationChannel(channel)
60
- }
61
- application.registerActivityLifecycleCallbacks(this )
62
- }
63
-
64
- /* *
65
- * Requests permission to show notifications for this application.
66
- *
67
- * This must be called during [Activity.onCreate].
68
- * [enable] should then be called when you want to actually show the notification.
69
- *
70
- * @param activity the [Activity] object
71
- */
72
- @RequiresApi(Build .VERSION_CODES .TIRAMISU )
73
- fun <T > requestPermission (activity : T ) where T : Activity, T : ActivityResultCaller {
74
- if (ContextCompat .checkSelfPermission(activity, POST_NOTIFICATIONS ) == PERMISSION_GRANTED ) {
75
- Log .i(TAG , " Already has permission." )
76
- return
77
- }
78
-
79
- if (hasRequestedPermission) {
80
- Log .i(TAG , " Already request permission; Not trying again." )
81
- return
82
- }
43
+ fun showNotification (activity : Activity ) {
44
+ synchronized(this ) {
45
+ if (ContextCompat .checkSelfPermission(activity, POST_NOTIFICATIONS ) == PERMISSION_DENIED ) {
46
+ Log .w(TAG , " Not showing notification because permission has not been granted." )
47
+ return
48
+ }
83
49
84
- val launcher = activity.registerForActivityResult(ActivityResultContracts .RequestPermission ()) {
85
- isGranted: Boolean ->
86
- if (! isEnabled) {
87
- Log .w(TAG , " Trigger disabled after permission check. Abandoning notification." )
88
- } else if (isGranted) {
89
- showNotification(activity)
90
- } else {
91
- Log .i(TAG , " Permission not granted" )
92
- // TODO: Ideally we would show a message indicating the impact of not
93
- // enabling the permission, but there's no way to know if they've
94
- // permanently denied the permission, and we don't want to show them a
95
- // message after each time we try to post a notification.
50
+ // Create the NotificationChannel, but only on API 26+ because
51
+ // the NotificationChannel class is new and not in the support library
52
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
53
+ val channel =
54
+ NotificationChannel (
55
+ FEEDBACK_NOTIFICATION_CHANNEL_ID ,
56
+ activity.getString(R .string.feedbackTriggerNotificationChannelName),
57
+ NotificationManager .IMPORTANCE_HIGH
58
+ )
59
+ channel.description =
60
+ activity.getString(R .string.feedbackTriggerNotificationChannelDescription)
61
+ activity
62
+ .getSystemService(NotificationManager ::class .java)
63
+ .createNotificationChannel(channel)
96
64
}
97
- }
98
65
99
- if (activity.shouldShowRequestPermissionRationale(POST_NOTIFICATIONS )) {
100
- Log .i(TAG , " Showing customer rationale for requesting permission." )
101
- AlertDialog .Builder (activity)
102
- .setMessage(
103
- " Using a notification to initiate feedback to the developer. " +
104
- " To enable this feature, allow the app to post notifications."
66
+ val intent = Intent (activity, CustomNotificationFeedbackTriggerActivity ::class .java)
67
+ intent.addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
68
+ val pendingIntent =
69
+ PendingIntent .getActivity(
70
+ activity,
71
+ /* requestCode = */ 0 ,
72
+ intent,
73
+ PendingIntent .FLAG_IMMUTABLE
105
74
)
106
- .setPositiveButton(" OK" ) { _, _ ->
107
- Log .i(TAG , " Launching request for permission." )
108
- launcher.launch(POST_NOTIFICATIONS )
109
- }
110
- .show()
111
- } else {
112
- Log .i(TAG , " Launching request for permission without rationale." )
113
- launcher.launch(POST_NOTIFICATIONS )
75
+ val builder =
76
+ NotificationCompat .Builder (activity, FEEDBACK_NOTIFICATION_CHANNEL_ID )
77
+ .setSmallIcon(R .mipmap.ic_launcher)
78
+ .setContentTitle(activity.getText(R .string.feedbackTriggerNotificationTitle))
79
+ .setContentText(activity.getText(R .string.feedbackTriggerNotificationText))
80
+ .setPriority(NotificationCompat .PRIORITY_HIGH )
81
+ .setContentIntent(pendingIntent)
82
+ val notificationManager = NotificationManagerCompat .from(activity)
83
+ Log .i(TAG , " Showing notification" )
84
+ notificationManager.notify(FEEDBACK_NOTIFICATION_ID , builder.build())
85
+ activityToScreenshot = activity
114
86
}
115
- hasRequestedPermission = true
116
87
}
117
88
118
89
/* *
119
- * Show notifications.
120
- *
121
- * This could be called during [Activity.onCreate].
90
+ * Hide the notification.
122
91
*
123
- * @param activity the [Activity] object
92
+ * This must be called from the [Activity.onDestroy] of the activity showing the notification.
124
93
*/
125
- fun enable (activity : Activity ) {
126
- activityToScreenshot = activity
127
- isEnabled = true
128
- showNotification(activity)
129
- }
130
-
131
- /* * Hide notifications. */
132
- fun disable () {
133
- val activity = activityToScreenshot
134
- if (activity != null ) {
135
- cancelNotification(activity)
136
- }
137
- isEnabled = false
138
- activityToScreenshot = null
139
- }
140
-
141
- private fun showNotification (context : Context ) {
142
- if (ContextCompat .checkSelfPermission(context, POST_NOTIFICATIONS ) == PERMISSION_DENIED ) {
143
- Log .w(TAG , " Not showing notification because permission has not been granted." )
144
- return
145
- }
146
-
147
- val intent = Intent (context, TakeScreenshotAndTriggerFeedbackActivity ::class .java)
148
- intent.addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
149
- val pendingIntent =
150
- PendingIntent .getActivity(
151
- context,
152
- /* requestCode = */ 0 ,
153
- intent,
154
- PendingIntent .FLAG_IMMUTABLE
155
- )
156
- val builder =
157
- NotificationCompat .Builder (context, FEEDBACK_NOTIFICATION_CHANNEL_ID )
158
- .setSmallIcon(R .mipmap.ic_launcher)
159
- .setContentTitle(context.getText(R .string.feedbackTriggerNotificationTitle))
160
- .setContentText(context.getText(R .string.feedbackTriggerNotificationText))
161
- .setPriority(NotificationCompat .PRIORITY_HIGH )
162
- .setContentIntent(pendingIntent)
163
- val notificationManager = NotificationManagerCompat .from(context)
164
- Log .i(TAG , " Showing notification" )
165
- notificationManager.notify(FEEDBACK_NOTIFICATION_ID , builder.build())
166
- }
167
-
168
- private fun cancelNotification (context : Context ) {
169
- val notificationManager = NotificationManagerCompat .from(context)
170
- Log .i(TAG , " Cancelling notification" )
171
- notificationManager.cancel(FEEDBACK_NOTIFICATION_ID )
172
- }
173
-
174
- override fun onActivityResumed (activity : Activity ) {
175
- if (isEnabled) {
176
- if (activity !is TakeScreenshotAndTriggerFeedbackActivity ) {
177
- Log .d(TAG , " setting current activity" )
178
- activityToScreenshot = activity
94
+ fun cancelNotification () {
95
+ synchronized(this ) {
96
+ activityToScreenshot?.let {
97
+ Log .i(TAG , " Cancelling notification" )
98
+ NotificationManagerCompat .from(it).cancel(FEEDBACK_NOTIFICATION_ID )
179
99
}
180
100
}
181
101
}
182
-
183
- override fun onActivityDestroyed (activity : Activity ) {
184
- if (activity == activityToScreenshot) {
185
- Log .d(TAG , " clearing current activity" )
186
- activityToScreenshot = null
187
- }
188
- }
189
-
190
- // Other lifecycle methods
191
- override fun onActivityCreated (activity : Activity , savedInstanceState : Bundle ? ) {}
192
- override fun onActivityStarted (activity : Activity ) {}
193
- override fun onActivityPaused (activity : Activity ) {}
194
- override fun onActivityStopped (activity : Activity ) {}
195
- override fun onActivitySaveInstanceState (activity : Activity , outState : Bundle ) {}
196
102
}
197
103
198
- class TakeScreenshotAndTriggerFeedbackActivity : Activity () {
104
+ class CustomNotificationFeedbackTriggerActivity : Activity () {
199
105
override fun onCreate (savedInstanceState : Bundle ? ) {
200
106
super .onCreate(savedInstanceState)
201
- val activity = CustomNotificationFeedbackTrigger .activityToScreenshot
202
- if (activity == null ) {
203
- Log .e(TAG , " Can't take screenshot because activity is unknown" )
204
- return
107
+ synchronized(CustomNotificationFeedbackTrigger ) {
108
+ val activity = CustomNotificationFeedbackTrigger .activityToScreenshot
109
+ if (activity == null ) {
110
+ Log .e(TAG , " Can't take screenshot because activity is unknown" )
111
+ return
112
+ }
113
+ takeScreenshot(activity)
205
114
}
206
- takeScreenshot(activity)
207
115
}
208
116
209
117
override fun onResume () {
0 commit comments