Skip to content

Commit 071f82e

Browse files
committed
Simplify custom notification trigger example and move to activity
1 parent 78fbf7e commit 071f82e

File tree

7 files changed

+180
-185
lines changed

7 files changed

+180
-185
lines changed

firebase-appdistribution/test-app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
</activity>
2020
<activity android:name="com.googletest.firebase.appdistribution.testapp.SecondActivity" />
2121
<activity android:name="com.googletest.firebase.appdistribution.testapp.ScreenshotDetectionActivity" />
22-
<activity android:name="com.googletest.firebase.appdistribution.testapp.TakeScreenshotAndTriggerFeedbackActivity" />
22+
<activity android:name="com.googletest.firebase.appdistribution.testapp.CustomNotificationActivity" />
23+
<activity android:name="com.googletest.firebase.appdistribution.testapp.CustomNotificationFeedbackTriggerActivity" />
2324
</application>
2425
</manifest>

firebase-appdistribution/test-app/src/main/java/com/googletest/firebase/appdistribution/testapp/AppDistroTestApplication.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@ class AppDistroTestApplication : Application() {
66
override fun onCreate() {
77
super.onCreate()
88

9-
// Perform any required trigger initialization here
10-
CustomNotificationFeedbackTrigger.initialize(this);
11-
129
// Default feedback triggers can optionally be enabled application-wide here
1310
// ShakeDetectionFeedbackTrigger.enable(this)
1411
// ScreenshotDetectionFeedbackTrigger.enable()
15-
// NotificationFeedbackTrigger.enable()
1612
}
1713
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.googletest.firebase.appdistribution.testapp
2+
3+
import android.Manifest.permission.POST_NOTIFICATIONS
4+
import android.app.AlertDialog
5+
import android.content.pm.PackageManager.PERMISSION_DENIED
6+
import android.os.Bundle
7+
import android.util.Log
8+
import androidx.activity.result.contract.ActivityResultContracts
9+
import androidx.appcompat.app.AppCompatActivity
10+
import androidx.core.content.ContextCompat
11+
import java.util.*
12+
13+
class CustomNotificationActivity : AppCompatActivity() {
14+
15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(savedInstanceState)
17+
setContentView(R.layout.activity_custom_notification)
18+
19+
val launcher =
20+
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
21+
if (isGranted) {
22+
Log.i(TAG, "Permission granted, showing notification")
23+
CustomNotificationFeedbackTrigger.showNotification(this)
24+
} else {
25+
Log.i(TAG, "Permission not granted")
26+
AlertDialog.Builder(this)
27+
.setMessage(
28+
"Because the notification permission has been denied, the app will not show a " +
29+
"notification that can be tapped to send feedback to the developer."
30+
)
31+
.setPositiveButton("OK") { _, _ -> }
32+
.show()
33+
}
34+
}
35+
36+
if (ContextCompat.checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_DENIED) {
37+
if (shouldShowRequestPermissionRationale(POST_NOTIFICATIONS)) {
38+
Log.i(TAG, "Showing customer rationale for requesting permission.")
39+
AlertDialog.Builder(this)
40+
.setMessage(
41+
"Using a notification to initiate feedback to the developer. " +
42+
"To enable this feature, allow the app to post notifications."
43+
)
44+
.setPositiveButton("OK") { _, _ ->
45+
Log.i(TAG, "Launching request for permission.")
46+
launcher.launch(POST_NOTIFICATIONS)
47+
}
48+
.setNegativeButton("No thanks") { _, _ -> Log.i(TAG, "User denied permission request.") }
49+
.show()
50+
} else {
51+
Log.i(TAG, "Launching request for permission without rationale.")
52+
launcher.launch(POST_NOTIFICATIONS)
53+
}
54+
} else {
55+
CustomNotificationFeedbackTrigger.showNotification(this)
56+
}
57+
}
58+
59+
override fun onDestroy() {
60+
CustomNotificationFeedbackTrigger.cancelNotification()
61+
super.onDestroy()
62+
}
63+
64+
companion object {
65+
private const val TAG = "CustomNotificationActivity"
66+
}
67+
}

firebase-appdistribution/test-app/src/main/java/com/googletest/firebase/appdistribution/testapp/CustomNotificationFeedbackTrigger.kt

Lines changed: 66 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -6,204 +6,112 @@ import android.app.*
66
import android.content.Context
77
import android.content.Intent
88
import android.content.pm.PackageManager.PERMISSION_DENIED
9-
import android.content.pm.PackageManager.PERMISSION_GRANTED
109
import android.graphics.Bitmap
1110
import android.graphics.Canvas
1211
import android.net.Uri
1312
import android.os.Build
1413
import android.os.Bundle
1514
import android.util.Log
16-
import androidx.activity.result.ActivityResultCaller
17-
import androidx.activity.result.contract.ActivityResultContracts
18-
import androidx.annotation.RequiresApi
1915
import androidx.core.app.NotificationCompat
2016
import androidx.core.app.NotificationManagerCompat
2117
import androidx.core.content.ContextCompat
2218
import com.google.firebase.appdistribution.ktx.appDistribution
2319
import com.google.firebase.ktx.Firebase
2420
import java.io.IOException
2521

22+
/**
23+
* Shows an ongoing notification that the user can tap to take a screenshot and send feedback to the
24+
* developer.
25+
*/
2626
@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"
3030
private const val FEEDBACK_NOTIFICATION_ID = 1
3131

32-
private var isEnabled = false
33-
private var hasRequestedPermission = false
32+
var activityToScreenshot: Activity? = null
3433

35-
internal var activityToScreenshot: Activity? = null
3634

3735
/**
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.
3938
*
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].
4240
*
43-
* @param application the [Application] object
41+
* @param activity the current activity, which will be captured by the screenshot
4442
*/
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+
}
8349

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)
9664
}
97-
}
9865

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
10574
)
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
11486
}
115-
hasRequestedPermission = true
11687
}
11788

11889
/**
119-
* Show notifications.
120-
*
121-
* This could be called during [Activity.onCreate].
90+
* Hide the notification.
12291
*
123-
* @param activity the [Activity] object
92+
* This must be called from the [Activity.onDestroy] of the activity showing the notification.
12493
*/
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)
17999
}
180100
}
181101
}
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) {}
196102
}
197103

198-
class TakeScreenshotAndTriggerFeedbackActivity : Activity() {
104+
class CustomNotificationFeedbackTriggerActivity : Activity() {
199105
override fun onCreate(savedInstanceState: Bundle?) {
200106
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)
205114
}
206-
takeScreenshot(activity)
207115
}
208116

209117
override fun onResume() {

firebase-appdistribution/test-app/src/main/java/com/googletest/firebase/appdistribution/testapp/MainActivity.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ class MainActivity : AppCompatActivity() {
6666
signInStatus = findViewById(R.id.sign_in_status)
6767
progressBar = findViewById(R.id.progress_bar)
6868

69-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
70-
CustomNotificationFeedbackTrigger.requestPermission(this)
71-
}
72-
7369
// Set up feedback trigger menu
7470
feedbackTriggerMenu = findViewById(R.id.feedbackTriggerMenu)
7571
val items = listOf(
@@ -97,8 +93,10 @@ class MainActivity : AppCompatActivity() {
9793
}
9894
FeedbackTrigger.CUSTOM_NOTIFICATION.label -> {
9995
disableAllFeedbackTriggers()
100-
Log.i(TAG, "Enabling notification trigger (custom)")
101-
CustomNotificationFeedbackTrigger.enable(this)
96+
startActivity(Intent(this, CustomNotificationActivity::class.java))
97+
// Set the selection back to None since once we're back to this activity the
98+
// trigger will be disabled
99+
autoCompleteTextView.setText(FeedbackTrigger.NONE.label, false)
102100
}
103101
FeedbackTrigger.SHAKE.label -> {
104102
disableAllFeedbackTriggers()
@@ -124,7 +122,6 @@ class MainActivity : AppCompatActivity() {
124122
private fun disableAllFeedbackTriggers() {
125123
Log.i(TAG, "Disabling all feedback triggers")
126124
firebaseAppDistribution.cancelFeedbackNotification()
127-
CustomNotificationFeedbackTrigger.disable()
128125
ShakeDetectionFeedbackTrigger.disable(application)
129126
}
130127

0 commit comments

Comments
 (0)