Skip to content

Commit 9d9f921

Browse files
committed
Simplify custom notification trigger example and move to activity (#4255)
* Simplify custom notification trigger example and move to activity * Remove synchronized blocks
1 parent fedec78 commit 9d9f921

File tree

9 files changed

+153
-170
lines changed

9 files changed

+153
-170
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.CustomNotificationTakeScreenshotActivity" />
2324
</application>
2425
</manifest>

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

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

9-
// Perform any required trigger initialization here
10-
CustomNotificationFeedbackTrigger.initialize(this);
11-
12-
// Default feedback triggers can optionally be enabled application-wide here
9+
// The shake detection feedback trigger can optionally be enabled application-wide here
1310
// ShakeDetectionFeedbackTrigger.enable(this)
14-
// ScreenshotDetectionFeedbackTrigger.enable()
15-
// NotificationFeedbackTrigger.enable()
1611
}
1712
}
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: 39 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -6,196 +6,99 @@ 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) {
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+
4649
// Create the NotificationChannel, but only on API 26+ because
4750
// the NotificationChannel class is new and not in the support library
4851
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
4952
val channel =
5053
NotificationChannel(
5154
FEEDBACK_NOTIFICATION_CHANNEL_ID,
52-
application.getString(R.string.feedbackTriggerNotificationChannelName),
55+
activity.getString(R.string.feedbackTriggerNotificationChannelName),
5356
NotificationManager.IMPORTANCE_HIGH
5457
)
5558
channel.description =
56-
application.getString(R.string.feedbackTriggerNotificationChannelDescription)
57-
application
59+
activity.getString(R.string.feedbackTriggerNotificationChannelDescription)
60+
activity
5861
.getSystemService(NotificationManager::class.java)
5962
.createNotificationChannel(channel)
6063
}
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-
}
7864

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)
14866
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
14967
val pendingIntent =
15068
PendingIntent.getActivity(
151-
context,
69+
activity,
15270
/* requestCode = */ 0,
15371
intent,
15472
PendingIntent.FLAG_IMMUTABLE
15573
)
15674
val builder =
157-
NotificationCompat.Builder(context, FEEDBACK_NOTIFICATION_CHANNEL_ID)
75+
NotificationCompat.Builder(activity, FEEDBACK_NOTIFICATION_CHANNEL_ID)
15876
.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))
16179
.setPriority(NotificationCompat.PRIORITY_HIGH)
16280
.setContentIntent(pendingIntent)
163-
val notificationManager = NotificationManagerCompat.from(context)
81+
.setOngoing(true)
82+
val notificationManager = NotificationManagerCompat.from(activity)
16483
Log.i(TAG, "Showing notification")
16584
notificationManager.notify(FEEDBACK_NOTIFICATION_ID, builder.build())
85+
activityToScreenshot = activity
16686
}
16787

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)
18797
}
18898
}
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) {}
19699
}
197100

198-
class TakeScreenshotAndTriggerFeedbackActivity : Activity() {
101+
class CustomNotificationTakeScreenshotActivity : Activity() {
199102
override fun onCreate(savedInstanceState: Bundle?) {
200103
super.onCreate(savedInstanceState)
201104
val activity = CustomNotificationFeedbackTrigger.activityToScreenshot

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,6 @@ class MainActivity : AppCompatActivity() {
6969
signInStatus = findViewById(R.id.sign_in_status)
7070
progressBar = findViewById(R.id.progress_bar)
7171

72-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
73-
CustomNotificationFeedbackTrigger.requestPermission(this)
74-
}
75-
7672
// Set up feedback trigger menu
7773
feedbackTriggerMenu = findViewById(R.id.feedbackTriggerMenu)
7874
val items = listOf(
@@ -100,8 +96,7 @@ class MainActivity : AppCompatActivity() {
10096
}
10197
FeedbackTrigger.CUSTOM_NOTIFICATION.label -> {
10298
disableAllFeedbackTriggers()
103-
Log.i(TAG, "Enabling notification trigger (custom)")
104-
CustomNotificationFeedbackTrigger.enable(this)
99+
startActivity(Intent(this, CustomNotificationActivity::class.java))
105100
}
106101
FeedbackTrigger.SHAKE.label -> {
107102
disableAllFeedbackTriggers()
@@ -111,9 +106,6 @@ class MainActivity : AppCompatActivity() {
111106
FeedbackTrigger.SCREENSHOT.label -> {
112107
disableAllFeedbackTriggers()
113108
startActivity(Intent(this, ScreenshotDetectionActivity::class.java))
114-
// Set the selection back to None since once we're back to this activity the
115-
// trigger will be disabled
116-
autoCompleteTextView.setText(FeedbackTrigger.NONE.label, false)
117109
}
118110
}
119111
}
@@ -127,7 +119,6 @@ class MainActivity : AppCompatActivity() {
127119
private fun disableAllFeedbackTriggers() {
128120
Log.i(TAG, "Disabling all feedback triggers")
129121
firebaseAppDistribution.cancelFeedbackNotification()
130-
CustomNotificationFeedbackTrigger.disable()
131122
ShakeDetectionFeedbackTrigger.disable(application)
132123
}
133124

0 commit comments

Comments
 (0)