@@ -6,196 +6,99 @@ 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 ) {
43
+ fun showNotification (activity : Activity ) {
44
+ if (ContextCompat .checkSelfPermission(activity, POST_NOTIFICATIONS ) == PERMISSION_DENIED ) {
45
+ Log .w(TAG , " Not showing notification because permission has not been granted." )
46
+ return
47
+ }
48
+
46
49
// Create the NotificationChannel, but only on API 26+ because
47
50
// the NotificationChannel class is new and not in the support library
48
51
if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
49
52
val channel =
50
53
NotificationChannel (
51
54
FEEDBACK_NOTIFICATION_CHANNEL_ID ,
52
- application .getString(R .string.feedbackTriggerNotificationChannelName),
55
+ activity .getString(R .string.feedbackTriggerNotificationChannelName),
53
56
NotificationManager .IMPORTANCE_HIGH
54
57
)
55
58
channel.description =
56
- application .getString(R .string.feedbackTriggerNotificationChannelDescription)
57
- application
59
+ activity .getString(R .string.feedbackTriggerNotificationChannelDescription)
60
+ activity
58
61
.getSystemService(NotificationManager ::class .java)
59
62
.createNotificationChannel(channel)
60
63
}
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
64
79
- if (hasRequestedPermission) {
80
- Log .i(TAG , " Already request permission; Not trying again." )
81
- return
82
- }
83
-
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.
96
- }
97
- }
98
-
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."
105
- )
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 )
114
- }
115
- hasRequestedPermission = true
116
- }
117
-
118
- /* *
119
- * Show notifications.
120
- *
121
- * This could be called during [Activity.onCreate].
122
- *
123
- * @param activity the [Activity] object
124
- */
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)
65
+ val intent = Intent (activity, CustomNotificationTakeScreenshotActivity ::class .java)
148
66
intent.addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
149
67
val pendingIntent =
150
68
PendingIntent .getActivity(
151
- context ,
69
+ activity ,
152
70
/* requestCode = */ 0 ,
153
71
intent,
154
72
PendingIntent .FLAG_IMMUTABLE
155
73
)
156
74
val builder =
157
- NotificationCompat .Builder (context , FEEDBACK_NOTIFICATION_CHANNEL_ID )
75
+ NotificationCompat .Builder (activity , FEEDBACK_NOTIFICATION_CHANNEL_ID )
158
76
.setSmallIcon(R .mipmap.ic_launcher)
159
- .setContentTitle(context .getText(R .string.feedbackTriggerNotificationTitle))
160
- .setContentText(context .getText(R .string.feedbackTriggerNotificationText))
77
+ .setContentTitle(activity .getText(R .string.feedbackTriggerNotificationTitle))
78
+ .setContentText(activity .getText(R .string.feedbackTriggerNotificationText))
161
79
.setPriority(NotificationCompat .PRIORITY_HIGH )
162
80
.setContentIntent(pendingIntent)
163
- val notificationManager = NotificationManagerCompat .from(context)
81
+ .setOngoing(true )
82
+ val notificationManager = NotificationManagerCompat .from(activity)
164
83
Log .i(TAG , " Showing notification" )
165
84
notificationManager.notify(FEEDBACK_NOTIFICATION_ID , builder.build())
85
+ activityToScreenshot = activity
166
86
}
167
87
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
179
- }
180
- }
181
- }
182
-
183
- override fun onActivityDestroyed (activity : Activity ) {
184
- if (activity == activityToScreenshot) {
185
- Log .d(TAG , " clearing current activity" )
186
- activityToScreenshot = null
88
+ /* *
89
+ * Hide the notification.
90
+ *
91
+ * This must be called from the [Activity.onDestroy] of the activity showing the notification.
92
+ */
93
+ fun cancelNotification () {
94
+ activityToScreenshot?.let {
95
+ Log .i(TAG , " Cancelling notification" )
96
+ NotificationManagerCompat .from(it).cancel(FEEDBACK_NOTIFICATION_ID )
187
97
}
188
98
}
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
99
}
197
100
198
- class TakeScreenshotAndTriggerFeedbackActivity : Activity () {
101
+ class CustomNotificationTakeScreenshotActivity : Activity () {
199
102
override fun onCreate (savedInstanceState : Bundle ? ) {
200
103
super .onCreate(savedInstanceState)
201
104
val activity = CustomNotificationFeedbackTrigger .activityToScreenshot
0 commit comments