14
14
15
15
package com .google .firebase .appdistribution .impl ;
16
16
17
+ import static android .content .pm .PackageManager .PERMISSION_GRANTED ;
17
18
import static com .google .firebase .appdistribution .FirebaseAppDistributionException .Status .AUTHENTICATION_CANCELED ;
18
19
import static com .google .firebase .appdistribution .FirebaseAppDistributionException .Status .AUTHENTICATION_FAILURE ;
19
20
import static com .google .firebase .appdistribution .FirebaseAppDistributionException .Status .HOST_ACTIVITY_INTERRUPTED ;
24
25
import static com .google .firebase .appdistribution .impl .TaskUtils .safeSetTaskException ;
25
26
import static com .google .firebase .appdistribution .impl .TaskUtils .safeSetTaskResult ;
26
27
28
+ import android .Manifest ;
27
29
import android .app .Activity ;
28
30
import android .app .AlertDialog ;
31
+ import android .app .NotificationChannel ;
32
+ import android .app .NotificationChannelGroup ;
33
+ import android .app .NotificationManager ;
34
+ import android .app .PendingIntent ;
29
35
import android .content .Context ;
30
36
import android .content .Intent ;
37
+ import android .content .pm .ApplicationInfo ;
38
+ import android .content .pm .PackageManager ;
31
39
import android .net .Uri ;
40
+ import android .os .Build ;
41
+ import androidx .activity .result .ActivityResultCaller ;
42
+ import androidx .activity .result .ActivityResultLauncher ;
43
+ import androidx .activity .result .contract .ActivityResultContracts ;
32
44
import androidx .annotation .GuardedBy ;
33
45
import androidx .annotation .NonNull ;
34
46
import androidx .annotation .Nullable ;
47
+ import androidx .annotation .RequiresApi ;
48
+ import androidx .annotation .RequiresPermission ;
35
49
import androidx .annotation .VisibleForTesting ;
50
+ import androidx .core .app .ActivityCompat ;
51
+ import androidx .core .app .NotificationCompat ;
52
+ import androidx .core .app .NotificationManagerCompat ;
53
+ import androidx .core .content .ContextCompat ;
36
54
import com .google .android .gms .tasks .Task ;
37
55
import com .google .android .gms .tasks .TaskCompletionSource ;
38
56
import com .google .android .gms .tasks .Tasks ;
55
73
class FirebaseAppDistributionImpl implements FirebaseAppDistribution {
56
74
57
75
private static final int UNKNOWN_RELEASE_FILE_SIZE = -1 ;
76
+ private static final String FEEDBACK_NOTIFICATION_CHANNEL_ID = "InAppFeedbackNotification" ;
77
+ private static final int FEEDBACK_NOTIFICATION_ID = 1 ;
58
78
59
79
private final FirebaseApp firebaseApp ;
60
80
private final TesterSignInManager testerSignInManager ;
@@ -340,7 +360,7 @@ public void startFeedback(@NonNull CharSequence infoText) {
340
360
341
361
@ Override
342
362
public void startFeedback (@ NonNull int infoTextResourceId , @ Nullable Uri screenshotUri ) {
343
- startFeedback (firebaseApp . getApplicationContext (). getText (infoTextResourceId ), screenshotUri );
363
+ startFeedback (getText (infoTextResourceId ), screenshotUri );
344
364
}
345
365
346
366
@ Override
@@ -353,6 +373,120 @@ public void startFeedback(@NonNull CharSequence infoText, @Nullable Uri screensh
353
373
doStartFeedback (infoText , screenshotUri );
354
374
}
355
375
376
+ @ Override
377
+ public void showFeedbackNotification (@ NonNull int infoTextResourceId , int importance ) {
378
+ showFeedbackNotification (getText (infoTextResourceId ), importance );
379
+ }
380
+
381
+ @ Override
382
+ public void showFeedbackNotification (@ NonNull CharSequence infoText , int importance ) {
383
+ Context context = firebaseApp .getApplicationContext ();
384
+
385
+ // Create the NotificationChannel, but only on API 26+ because
386
+ // the NotificationChannel class is new and not in the support library
387
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
388
+ NotificationChannel channel =
389
+ new NotificationChannel (
390
+ FEEDBACK_NOTIFICATION_CHANNEL_ID ,
391
+ context .getString (R .string .feedback_notification_channel_name ),
392
+ importance
393
+ );
394
+ channel .setDescription (context .getString (R .string .feedback_notification_channel_description ));
395
+ context .getSystemService (NotificationManager .class ).createNotificationChannel (channel );
396
+ }
397
+
398
+ if (ContextCompat .checkSelfPermission (context , Manifest .permission .POST_NOTIFICATIONS )
399
+ == PERMISSION_GRANTED ) {
400
+ showNotification (importance );
401
+ } else {
402
+ LogWrapper .getInstance ()
403
+ .w ("Not showing notification because permission has not been granted." );
404
+ }
405
+ }
406
+
407
+ @ RequiresPermission (value = Manifest .permission .POST_NOTIFICATIONS )
408
+ private void showNotification (int importance ) {
409
+ Context context = firebaseApp .getApplicationContext ();
410
+ Intent intent = new Intent (context , TakeScreenshotAndStartFeedbackActivity .class );
411
+ intent .addFlags (Intent .FLAG_ACTIVITY_NO_HISTORY );
412
+ PendingIntent pendingIntent =
413
+ PendingIntent .getActivity (
414
+ context ,
415
+ /* requestCode = */ 0 ,
416
+ intent ,
417
+ PendingIntent .FLAG_IMMUTABLE );
418
+ ApplicationInfo applicationInfo = context .getApplicationInfo ();
419
+ PackageManager packageManager = context .getPackageManager ();
420
+ CharSequence appLabel = packageManager .getApplicationLabel (applicationInfo );
421
+ NotificationCompat .Builder builder =
422
+ new NotificationCompat .Builder (context , FEEDBACK_NOTIFICATION_CHANNEL_ID )
423
+ .setSmallIcon (R .mipmap .test_adaptive_icon ) // TODO: update to an appropriate icon
424
+ .setContentTitle (context .getString (R .string .feedback_notification_title ))
425
+ .setContentText (context .getString (R .string .feedback_notification_text , appLabel ))
426
+ .setPriority (convertImportanceToPriority (importance ))
427
+ .setContentIntent (pendingIntent );
428
+ NotificationManagerCompat notificationManager = NotificationManagerCompat .from (context );
429
+ LogWrapper .getInstance ().i ("Showing feedback notification." );
430
+ notificationManager .notify (FEEDBACK_NOTIFICATION_ID , builder .build ());
431
+ }
432
+
433
+ private int convertImportanceToPriority (int importance ) {
434
+ switch (importance ) {
435
+ case NotificationManagerCompat .IMPORTANCE_MIN :
436
+ return NotificationCompat .PRIORITY_MIN ;
437
+ case NotificationManagerCompat .IMPORTANCE_LOW :
438
+ return NotificationCompat .PRIORITY_LOW ;
439
+ case NotificationManagerCompat .IMPORTANCE_HIGH :
440
+ return NotificationCompat .PRIORITY_HIGH ;
441
+ case NotificationManagerCompat .IMPORTANCE_MAX :
442
+ return NotificationCompat .PRIORITY_MAX ;
443
+ case NotificationManagerCompat .IMPORTANCE_UNSPECIFIED :
444
+ case NotificationManagerCompat .IMPORTANCE_NONE :
445
+ case NotificationManagerCompat .IMPORTANCE_DEFAULT :
446
+ default :
447
+ return NotificationCompat .PRIORITY_DEFAULT ;
448
+ }
449
+ }
450
+
451
+ @ Override
452
+ @ RequiresApi (Build .VERSION_CODES .TIRAMISU )
453
+ public <T extends Activity & ActivityResultCaller > void requestNotificationPermissions (
454
+ @ NonNull T activity ) {
455
+ if (ContextCompat .checkSelfPermission (activity , Manifest .permission .POST_NOTIFICATIONS )
456
+ == PERMISSION_GRANTED ) {
457
+ LogWrapper .getInstance ().i ("Already has permission to show notifications." );
458
+ return ;
459
+ }
460
+
461
+ ActivityResultLauncher <String > launcher = activity .registerForActivityResult (
462
+ new ActivityResultContracts .RequestPermission (), isGranted -> {
463
+ if (!isGranted ) {
464
+ LogWrapper .getInstance ().w ("Permission to show notifications was denied." );
465
+ // Ideally we would show a message indicating the impact of not enabling the permission,
466
+ // but there's no way to know if they've permanently denied the permission, and we don't
467
+ // want to show them a message after each time we try to post a notification.
468
+ }
469
+ });
470
+
471
+ // TODO: should we bother showing permission rationale?
472
+ if (ActivityCompat .shouldShowRequestPermissionRationale (activity ,
473
+ Manifest .permission .POST_NOTIFICATIONS )) {
474
+ LogWrapper .getInstance ().i ("Showing customer rationale for requesting permission." );
475
+ new AlertDialog .Builder (activity )
476
+ .setMessage (R .string .notification_permission_rationale )
477
+ .setPositiveButton (R .string .notification_permission_yes_button , (a , b ) -> {
478
+ LogWrapper .getInstance ().i ("Launching request for permission." );
479
+ launcher .launch (Manifest .permission .POST_NOTIFICATIONS );
480
+ })
481
+ .setNegativeButton (R .string .notification_permission_no_button ,
482
+ (a , b ) -> LogWrapper .getInstance ().i ("Tester declined to enable notifications." ))
483
+ .show ();
484
+ } else {
485
+ LogWrapper .getInstance ().i ("Launching request for permission without rationale." );
486
+ launcher .launch (Manifest .permission .POST_NOTIFICATIONS );
487
+ }
488
+ }
489
+
356
490
private void doStartFeedback (CharSequence infoText , @ Nullable Uri screenshotUri ) {
357
491
testerSignInManager
358
492
.signInTester ()
@@ -569,4 +703,8 @@ private boolean awaitingUpdateDialogConfirmation() {
569
703
&& !showUpdateDialogTask .getTask ().isComplete ()
570
704
&& remakeUpdateConfirmationDialog );
571
705
}
706
+
707
+ private CharSequence getText (int resourceId ) {
708
+ return firebaseApp .getApplicationContext ().getText (resourceId );
709
+ }
572
710
}
0 commit comments