Skip to content

Commit 056109c

Browse files
authored
Merge bfa9509 into 398f16a
2 parents 398f16a + bfa9509 commit 056109c

File tree

6 files changed

+257
-42
lines changed

6 files changed

+257
-42
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/AppDistroComponent.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import dagger.Component;
3030
import dagger.Module;
3131
import java.util.concurrent.Executor;
32+
import java.util.concurrent.ExecutorService;
33+
import java.util.concurrent.ScheduledExecutorService;
3234
import javax.inject.Singleton;
3335

3436
@Component(modules = AppDistroComponent.MainModule.class)
@@ -63,13 +65,13 @@ interface Builder {
6365
Builder setFis(Provider<FirebaseInstallationsApi> fis);
6466

6567
@BindsInstance
66-
Builder setBackgroundExecutor(@Background Executor executor);
68+
Builder setBackgroundExecutor(@Background ScheduledExecutorService executor);
6769

6870
@BindsInstance
69-
Builder setBlockingExecutor(@Blocking Executor executor);
71+
Builder setBlockingExecutor(@Blocking ScheduledExecutorService executor);
7072

7173
@BindsInstance
72-
Builder setLightweightExecutor(@Lightweight Executor executor);
74+
Builder setLightweightExecutor(@Lightweight ScheduledExecutorService executor);
7375

7476
@BindsInstance
7577
Builder setUiThreadExecutor(@UiThread Executor executor);
@@ -86,5 +88,29 @@ interface Builder {
8688
interface MainModule {
8789
@Binds
8890
FirebaseAppDistribution bindAppDistro(FirebaseAppDistributionImpl impl);
91+
92+
@Binds
93+
@Background
94+
ExecutorService bindBackgroundExecutorService(@Background ScheduledExecutorService ses);
95+
96+
@Binds
97+
@Background
98+
Executor bindBackgroundExecutor(@Background ExecutorService es);
99+
100+
@Binds
101+
@Lightweight
102+
ExecutorService bindLightweightExecutorService(@Lightweight ScheduledExecutorService ses);
103+
104+
@Binds
105+
@Lightweight
106+
Executor bindLightweightExecutor(@Lightweight ExecutorService es);
107+
108+
@Binds
109+
@Blocking
110+
ExecutorService bindBlockingExecutorService(@Blocking ScheduledExecutorService ses);
111+
112+
@Binds
113+
@Blocking
114+
Executor bindBlockingExecutor(@Blocking ExecutorService es);
89115
}
90116
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionNotificationsManager.java

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
package com.google.firebase.appdistribution.impl;
1616

17+
import static com.google.firebase.appdistribution.impl.FirebaseAppDistributionNotificationsManager.NotificationType.APP_UPDATE;
18+
import static com.google.firebase.appdistribution.impl.FirebaseAppDistributionNotificationsManager.NotificationType.FEEDBACK;
19+
import static java.util.concurrent.TimeUnit.SECONDS;
20+
21+
import android.app.Activity;
22+
import android.app.Notification;
1723
import android.app.NotificationChannel;
1824
import android.app.NotificationChannelGroup;
1925
import android.app.PendingIntent;
@@ -28,10 +34,21 @@
2834
import androidx.annotation.VisibleForTesting;
2935
import androidx.core.app.NotificationCompat;
3036
import androidx.core.app.NotificationManagerCompat;
37+
import com.google.firebase.annotations.concurrent.Lightweight;
38+
import com.google.firebase.annotations.concurrent.UiThread;
3139
import com.google.firebase.appdistribution.InterruptionLevel;
40+
import com.google.firebase.appdistribution.impl.FirebaseAppDistributionLifecycleNotifier.OnActivityPausedListener;
41+
import com.google.firebase.appdistribution.impl.FirebaseAppDistributionLifecycleNotifier.OnActivityResumedListener;
42+
import java.util.concurrent.Executor;
43+
import java.util.concurrent.ScheduledExecutorService;
44+
import java.util.concurrent.ScheduledFuture;
3245
import javax.inject.Inject;
46+
import javax.inject.Singleton;
47+
48+
@Singleton
49+
class FirebaseAppDistributionNotificationsManager
50+
implements OnActivityPausedListener, OnActivityResumedListener {
3351

34-
class FirebaseAppDistributionNotificationsManager {
3552
private static final String TAG = "NotificationsManager";
3653

3754
private static final String PACKAGE_PREFIX = "com.google.firebase.appdistribution";
@@ -40,15 +57,15 @@ class FirebaseAppDistributionNotificationsManager {
4057
static final String CHANNEL_GROUP_ID = prependPackage("notification_channel_group_id");
4158

4259
@VisibleForTesting
43-
enum Notification {
60+
enum NotificationType {
4461
APP_UPDATE("notification_channel_id", "app_update_notification_tag"),
4562
FEEDBACK("feedback_notification_channel_id", "feedback_notification_tag");
4663

4764
final String channelId;
4865
final String tag;
4966
final int id;
5067

51-
Notification(String channelId, String tag) {
68+
NotificationType(String channelId, String tag) {
5269
this.channelId = prependPackage(channelId);
5370
this.tag = prependPackage(tag);
5471
this.id = ordinal();
@@ -58,12 +75,26 @@ enum Notification {
5875
private final Context context;
5976
private final AppIconSource appIconSource;
6077
private final NotificationManagerCompat notificationManager;
78+
@Lightweight private final ScheduledExecutorService scheduledExecutorService;
79+
@UiThread private final Executor uiThreadExecutor;
80+
81+
private Notification feedbackNotificationToBeShown;
82+
private ScheduledFuture<?> feedbackNotificationCancellationFuture;
6183

6284
@Inject
63-
FirebaseAppDistributionNotificationsManager(Context context, AppIconSource appIconSource) {
85+
FirebaseAppDistributionNotificationsManager(
86+
Context context,
87+
AppIconSource appIconSource,
88+
FirebaseAppDistributionLifecycleNotifier lifecycleNotifier,
89+
@Lightweight ScheduledExecutorService scheduledExecutorService,
90+
@UiThread Executor uiThreadExecutor) {
6491
this.context = context;
6592
this.appIconSource = appIconSource;
6693
this.notificationManager = NotificationManagerCompat.from(context);
94+
lifecycleNotifier.addOnActivityPausedListener(this);
95+
lifecycleNotifier.addOnActivityResumedListener(this);
96+
this.scheduledExecutorService = scheduledExecutorService;
97+
this.uiThreadExecutor = uiThreadExecutor;
6798
}
6899

69100
void showAppUpdateNotification(long totalBytes, long downloadedBytes, int stringResourceId) {
@@ -72,7 +103,7 @@ void showAppUpdateNotification(long totalBytes, long downloadedBytes, int string
72103
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
73104
LogWrapper.i(TAG, "Creating app update notification channel group");
74105
createChannel(
75-
Notification.APP_UPDATE,
106+
APP_UPDATE,
76107
R.string.app_update_notification_channel_name,
77108
R.string.app_update_notification_channel_description,
78109
InterruptionLevel.DEFAULT);
@@ -85,7 +116,7 @@ void showAppUpdateNotification(long totalBytes, long downloadedBytes, int string
85116
}
86117

87118
NotificationCompat.Builder notificationBuilder =
88-
new NotificationCompat.Builder(context, Notification.APP_UPDATE.channelId)
119+
new NotificationCompat.Builder(context, APP_UPDATE.channelId)
89120
.setOnlyAlertOnce(true)
90121
.setSmallIcon(appIconSource.getNonAdaptiveIconOrDefault(context))
91122
.setContentTitle(context.getString(stringResourceId))
@@ -97,8 +128,7 @@ void showAppUpdateNotification(long totalBytes, long downloadedBytes, int string
97128
if (appLaunchIntent != null) {
98129
notificationBuilder.setContentIntent(appLaunchIntent);
99130
}
100-
notificationManager.notify(
101-
Notification.APP_UPDATE.tag, Notification.APP_UPDATE.id, notificationBuilder.build());
131+
notificationManager.notify(APP_UPDATE.tag, APP_UPDATE.id, notificationBuilder.build());
102132
}
103133

104134
@Nullable
@@ -128,7 +158,7 @@ public void showFeedbackNotification(
128158
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
129159
LogWrapper.i(TAG, "Creating feedback notification channel group");
130160
createChannel(
131-
Notification.FEEDBACK,
161+
FEEDBACK,
132162
R.string.feedback_notification_channel_name,
133163
R.string.feedback_notification_channel_description,
134164
interruptionLevel);
@@ -139,36 +169,62 @@ public void showFeedbackNotification(
139169
return;
140170
}
141171

172+
uiThreadExecutor.execute(
173+
() -> {
174+
// ensure that class state is managed on same thread as lifecycle callbacks
175+
cancelFeedbackCancellationFuture();
176+
feedbackNotificationToBeShown = buildFeedbackNotification(infoText, interruptionLevel);
177+
doShowFeedbackNotification();
178+
});
179+
}
180+
181+
// this must be run on the main (UI) thread
182+
private void doShowFeedbackNotification() {
183+
LogWrapper.i(TAG, "Showing feedback notification");
184+
notificationManager.notify(FEEDBACK.tag, FEEDBACK.id, feedbackNotificationToBeShown);
185+
}
186+
187+
private Notification buildFeedbackNotification(
188+
@NonNull CharSequence infoText, @NonNull InterruptionLevel interruptionLevel) {
142189
Intent intent = new Intent(context, TakeScreenshotAndStartFeedbackActivity.class);
143190
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
144191
intent.putExtra(TakeScreenshotAndStartFeedbackActivity.INFO_TEXT_EXTRA_KEY, infoText);
145192
ApplicationInfo applicationInfo = context.getApplicationInfo();
146193
PackageManager packageManager = context.getPackageManager();
147194
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
148-
NotificationCompat.Builder builder =
149-
new NotificationCompat.Builder(context, Notification.FEEDBACK.channelId)
150-
.setSmallIcon(R.drawable.ic_baseline_rate_review_24)
151-
.setContentTitle(context.getString(R.string.feedback_notification_title))
152-
.setContentText(context.getString(R.string.feedback_notification_text, appLabel))
153-
.setPriority(interruptionLevel.notificationPriority)
154-
.setOngoing(true)
155-
.setOnlyAlertOnce(true)
156-
.setAutoCancel(false)
157-
.setContentIntent(getPendingIntent(intent, /* extraFlags= */ 0));
158-
LogWrapper.i(TAG, "Showing feedback notification");
159-
notificationManager.notify(
160-
Notification.FEEDBACK.tag, Notification.FEEDBACK.id, builder.build());
195+
return new NotificationCompat.Builder(context, FEEDBACK.channelId)
196+
.setSmallIcon(R.drawable.ic_baseline_rate_review_24)
197+
.setContentTitle(context.getString(R.string.feedback_notification_title))
198+
.setContentText(context.getString(R.string.feedback_notification_text, appLabel))
199+
.setPriority(interruptionLevel.notificationPriority)
200+
.setOngoing(true)
201+
.setOnlyAlertOnce(true)
202+
.setAutoCancel(false)
203+
.setContentIntent(getPendingIntent(intent, /* extraFlags= */ 0))
204+
.build();
161205
}
162206

163207
public void cancelFeedbackNotification() {
208+
uiThreadExecutor.execute(
209+
() -> {
210+
// ensure that class state is managed on same thread as lifecycle callbacks
211+
feedbackNotificationToBeShown = null;
212+
cancelFeedbackCancellationFuture();
213+
doCancelFeedbackNotification();
214+
});
215+
}
216+
217+
public void doCancelFeedbackNotification() {
164218
LogWrapper.i(TAG, "Cancelling feedback notification");
165-
NotificationManagerCompat.from(context)
166-
.cancel(Notification.FEEDBACK.tag, Notification.FEEDBACK.id);
219+
NotificationManagerCompat.from(context).cancel(FEEDBACK.tag, FEEDBACK.id);
167220
}
168221

169222
@RequiresApi(Build.VERSION_CODES.O)
170223
private void createChannel(
171-
Notification notification, int name, int description, InterruptionLevel interruptionLevel) {
224+
NotificationType notification,
225+
int name,
226+
int description,
227+
InterruptionLevel interruptionLevel) {
172228
notificationManager.createNotificationChannelGroup(
173229
new NotificationChannelGroup(
174230
CHANNEL_GROUP_ID, context.getString(R.string.notifications_group_name)));
@@ -182,6 +238,37 @@ private void createChannel(
182238
notificationManager.createNotificationChannel(channel);
183239
}
184240

241+
// this runs on the main (UI) thread
242+
@Override
243+
public void onPaused(Activity activity) {
244+
LogWrapper.d(TAG, "Activity paused");
245+
if (feedbackNotificationToBeShown != null) {
246+
LogWrapper.d(TAG, "Scheduling cancelFeedbackNotification");
247+
cancelFeedbackCancellationFuture();
248+
feedbackNotificationCancellationFuture =
249+
scheduledExecutorService.schedule(this::doCancelFeedbackNotification, 1, SECONDS);
250+
}
251+
}
252+
253+
// this runs on the main (UI) thread
254+
@Override
255+
public void onResumed(Activity activity) {
256+
LogWrapper.d(TAG, "Activity resumed");
257+
if (feedbackNotificationToBeShown != null) {
258+
cancelFeedbackCancellationFuture();
259+
doShowFeedbackNotification();
260+
}
261+
}
262+
263+
// this must be run on the main (UI) thread
264+
private void cancelFeedbackCancellationFuture() {
265+
if (feedbackNotificationCancellationFuture != null) {
266+
LogWrapper.d(TAG, "Canceling feedbackNotificationCancellationFuture");
267+
feedbackNotificationCancellationFuture.cancel(false);
268+
feedbackNotificationCancellationFuture = null;
269+
}
270+
}
271+
185272
private static String prependPackage(String id) {
186273
return String.format("%s.%s", PACKAGE_PREFIX, id);
187274
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionRegistrar.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Arrays;
3535
import java.util.List;
3636
import java.util.concurrent.Executor;
37+
import java.util.concurrent.ScheduledExecutorService;
3738

3839
/**
3940
* Registers FirebaseAppDistribution and related components.
@@ -48,10 +49,12 @@ public class FirebaseAppDistributionRegistrar implements ComponentRegistrar {
4849

4950
@Override
5051
public @NonNull List<Component<?>> getComponents() {
51-
Qualified<Executor> backgroundExecutor = Qualified.qualified(Background.class, Executor.class);
52-
Qualified<Executor> blockingExecutor = Qualified.qualified(Blocking.class, Executor.class);
53-
Qualified<Executor> lightweightExecutor =
54-
Qualified.qualified(Lightweight.class, Executor.class);
52+
Qualified<ScheduledExecutorService> backgroundExecutor =
53+
Qualified.qualified(Background.class, ScheduledExecutorService.class);
54+
Qualified<ScheduledExecutorService> blockingExecutor =
55+
Qualified.qualified(Blocking.class, ScheduledExecutorService.class);
56+
Qualified<ScheduledExecutorService> lightweightExecutor =
57+
Qualified.qualified(Lightweight.class, ScheduledExecutorService.class);
5558
Qualified<Executor> uiThreadExecutor = Qualified.qualified(UiThread.class, Executor.class);
5659
return Arrays.asList(
5760
Component.builder(FirebaseAppDistribution.class)

0 commit comments

Comments
 (0)