Skip to content

Commit 41e84f3

Browse files
authored
Merge branch 'master' into dconeybe/BloomFilterUnicodeTestSimplification
2 parents 8164144 + 4d1cc39 commit 41e84f3

File tree

13 files changed

+136
-89
lines changed

13 files changed

+136
-89
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version=18.3.8
1+
version=18.4.0
22
latestReleasedVersion=18.3.7
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version=18.3.8
1+
version=18.4.0
22
latestReleasedVersion=18.3.7
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=23.1.3
1+
version=23.2.0
22
latestReleasedVersion=23.1.2
33
android.enableUnitTestBinaryResources=true

firebase-messaging/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
[FAQ](https://firebase.google.com/support/faq#fcm-23-deprecation) for more
44
details.
55

6+
* [changed] Changed to finish a background broadcast after the message has been
7+
handled, subject to a timeout. This keeps the `FirebaseMessagingService`'s
8+
process in an active state while it is handling an FCM message, up to the
9+
20 seconds allowed.
10+
611
# 23.1.2
712
* [fixed] Fixed a breakage related to Jetpack core library related to an
813
[upstream update](https://android-review.googlesource.com/c/platform/frameworks/support/+/2399893).

firebase-messaging/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=23.1.3
1+
version=23.2.0
22
latestReleasedVersion=23.1.2
33
android.enableUnitTestBinaryResources=true

firebase-messaging/src/main/java/com/google/firebase/messaging/EnhancedIntentService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
*/
3737
@SuppressLint("UnwrappedWakefulBroadcastReceiver") // Not used within GmsCore
3838
public abstract class EnhancedIntentService extends Service {
39+
40+
// Allow apps 20 seconds to handle a message.
41+
static final long MESSAGE_TIMEOUT_S = 20;
42+
3943
private static final String TAG = "EnhancedIntentService";
4044

4145
// Use a different thread per service instance, so it can be reclaimed once the service is done.

firebase-messaging/src/main/java/com/google/firebase/messaging/FcmBroadcastProcessor.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public Task<Integer> startMessagingService(Context context, Intent intent) {
8787
boolean isHighPriority = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
8888

8989
if (subjectToBackgroundCheck && !isHighPriority) {
90-
return bindToMessagingService(context, intent);
90+
return bindToMessagingService(context, intent, isHighPriority);
9191
}
9292

9393
// If app isn't subject to background check or if message is high priority, use startService
@@ -106,28 +106,40 @@ public Task<Integer> startMessagingService(Context context, Intent intent) {
106106
// On O, if we're not able to start the service fall back to binding. This could happen if
107107
// the app isn't targeting O, but the user manually applied restrictions, or if the temp
108108
// whitelist has already expired.
109-
return bindToMessagingService(context, intent)
109+
return bindToMessagingService(context, intent, isHighPriority)
110110
.continueWith(
111111
// ok to use direct executor because we're just immediately returning an int
112112
Runnable::run,
113113
t -> ServiceStarter.ERROR_ILLEGAL_STATE_EXCEPTION_FALLBACK_TO_BIND);
114114
});
115115
}
116116

117-
private static Task<Integer> bindToMessagingService(Context context, Intent intent) {
117+
private static Task<Integer> bindToMessagingService(
118+
Context context, Intent intent, boolean isForegroundBroadcast) {
118119
if (Log.isLoggable(TAG, Log.DEBUG)) {
119120
Log.d(TAG, "Binding to service");
120121
}
121-
if (ServiceStarter.getInstance().hasWakeLockPermission(context)) {
122-
WakeLockHolder.sendWakefulServiceIntent(
123-
context, getServiceConnection(context, ServiceStarter.ACTION_MESSAGING_EVENT), intent);
122+
123+
WithinAppServiceConnection connection =
124+
getServiceConnection(context, ServiceStarter.ACTION_MESSAGING_EVENT);
125+
126+
if (isForegroundBroadcast) {
127+
// Foreground broadcast queue, finish the broadcast immediately
128+
// (by returning a completed Task) to avoid ANRs.
129+
if (ServiceStarter.getInstance().hasWakeLockPermission(context)) {
130+
WakeLockHolder.sendWakefulServiceIntent(context, connection, intent);
131+
} else {
132+
connection.sendIntent(intent);
133+
}
134+
return Tasks.forResult(ServiceStarter.SUCCESS);
124135
} else {
125-
// Ignore result since we're no longer blocking on the service handling the intent.
126-
Task<Void> unused =
127-
getServiceConnection(context, ServiceStarter.ACTION_MESSAGING_EVENT).sendIntent(intent);
136+
// Background broadcast queue, finish the broadcast after the message has been handled
137+
// (which times out after 20 seconds to avoid ANRs and to limit how long the app is active).
138+
return connection
139+
.sendIntent(intent)
140+
// ok to use direct executor because we're just immediately returning an int
141+
.continueWith(Runnable::run, t -> ServiceStarter.SUCCESS);
128142
}
129-
130-
return Tasks.forResult(ServiceStarter.SUCCESS);
131143
}
132144

133145
/** Connect to a service via bind. This is used to process intents in Android O+ */
@@ -151,4 +163,16 @@ public static void reset() {
151163
fcmServiceConn = null;
152164
}
153165
}
166+
167+
/**
168+
* Sets WithinAppServiceConnection for testing.
169+
*
170+
* @hide
171+
*/
172+
@VisibleForTesting
173+
public static void setServiceConnection(WithinAppServiceConnection connection) {
174+
synchronized (lock) {
175+
fcmServiceConn = connection;
176+
}
177+
}
154178
}

firebase-messaging/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,6 @@
6565
* <p>In order to receive Firebase messages, declare an implementation of <br>
6666
* {@link FirebaseMessagingService} in the app manifest. To process messages, override base class
6767
* methods to handle any events required by the application.
68-
*
69-
* <p>Client apps can send <a
70-
* href="https://firebase.google.com/docs/cloud-messaging/android/upstream">upstream messages</a>
71-
* back to the app server using the XMPP-based Cloud Connection Server. For example:
72-
*
73-
* <pre>
74-
* FirebaseMessaging.getInstance().send(
75-
* new RemoteMessage.Builder(SENDER_ID + "&#64;fcm.googleapis.com")
76-
* .setMessageId(id)
77-
* .addData("key", "value")
78-
* .build());</pre>
7968
*/
8069
public class FirebaseMessaging {
8170

@@ -447,7 +436,7 @@ public Task<Void> deleteToken() {
447436
* <p>The subscribe operation is persisted and will be retried until successful.
448437
*
449438
* <p>This uses an FCM registration token to identify the app instance, generating one if it does
450-
* not exist (see {@link #getToken()}), which perodically sends data to the Firebase backend when
439+
* not exist (see {@link #getToken()}), which periodically sends data to the Firebase backend when
451440
* auto-init is enabled. To delete the data, delete the token ({@link #deleteToken}) and the
452441
* Firebase Installations ID ({@code FirebaseInstallations.delete()}). To stop the periodic
453442
* sending of data, disable auto-init ({@link #setAutoInitEnabled}).
@@ -487,9 +476,9 @@ public Task<Void> unsubscribeFromTopic(@NonNull String topic) {
487476
* <p>When there is an active connection the message will be sent immediately, otherwise the
488477
* message will be queued up to the time to live (TTL) set in the message.
489478
*
490-
* @deprecated FCM upstream is deprecated and will be decommissioned in June 2024. Learn more in
491-
* the <a href="https://firebase.google.com/support/faq#fcm-23-deprecation">FAQ about FCM
492-
* features deprecated in June 2023</a>.
479+
* @deprecated FCM upstream messaging is deprecated and will be decommissioned in June 2024. Learn
480+
* more in the <a href="https://firebase.google.com/support/faq#fcm-23-deprecation">FAQ about
481+
* FCM features deprecated in June 2023</a>.
493482
*/
494483
@Deprecated
495484
public void send(@NonNull RemoteMessage message) {

firebase-messaging/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public class FirebaseMessagingService extends EnhancedIntentService {
8585
/**
8686
* Called when a message is received.
8787
*
88+
* <p>This should complete within 20 seconds. Taking longer may interfere with your ability to
89+
* complete your work and may affect pending messages.
90+
*
8891
* <p>This is also called when a notification message is received while the app is in the
8992
* foreground. The notification parameters can be retrieved with {@link
9093
* RemoteMessage#getNotification()}.
@@ -143,7 +146,6 @@ public void onSendError(@NonNull String msgId, @NonNull Exception exception) {}
143146
*/
144147
@WorkerThread
145148
public void onNewToken(@NonNull String token) {}
146-
;
147149

148150
/** @hide */
149151
@Override

firebase-messaging/src/main/java/com/google/firebase/messaging/WithinAppServiceConnection.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
package com.google.firebase.messaging;
1515

1616
import static com.google.firebase.messaging.FirebaseMessaging.TAG;
17-
import static com.google.firebase.messaging.WakeLockHolder.WAKE_LOCK_ACQUIRE_TIMEOUT_MILLIS;
1817

1918
import android.annotation.SuppressLint;
2019
import android.content.ComponentName;
@@ -30,6 +29,7 @@
3029
import com.google.android.gms.common.util.concurrent.NamedThreadFactory;
3130
import com.google.android.gms.tasks.Task;
3231
import com.google.android.gms.tasks.TaskCompletionSource;
32+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3333
import java.util.ArrayDeque;
3434
import java.util.Queue;
3535
import java.util.concurrent.ScheduledExecutorService;
@@ -43,10 +43,6 @@
4343
*/
4444
class WithinAppServiceConnection implements ServiceConnection {
4545

46-
// Time out WakeLock after 9s to match previous behavior of forcing the broadcast to finish after
47-
// that much time.
48-
private static final int REQUEST_TIMEOUT_MS = 9000;
49-
5046
static class BindRequest {
5147
final Intent intent;
5248
private final TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
@@ -56,22 +52,20 @@ static class BindRequest {
5652
}
5753

5854
void arrangeTimeout(ScheduledExecutorService executor) {
59-
// Allow the same maximum WakeLock duration for high priority messages as when using
60-
// startService(), otherwise allow up to 9 seconds to match the time that the service was
61-
// allowed to run before finishing the BroadcastReceiver.
62-
boolean isHighPriority = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
55+
// Timeout after 20 seconds by finishing the Task. This will finish a background broadcast,
56+
// which waits for the message to be handled.
6357
ScheduledFuture<?> timeoutFuture =
6458
executor.schedule(
6559
() -> {
6660
Log.w(
6761
TAG,
6862
"Service took too long to process intent: "
6963
+ intent.getAction()
70-
+ " Releasing WakeLock.");
64+
+ " finishing.");
7165
finish();
7266
},
73-
isHighPriority ? WAKE_LOCK_ACQUIRE_TIMEOUT_MILLIS : REQUEST_TIMEOUT_MS,
74-
TimeUnit.MILLISECONDS);
67+
EnhancedIntentService.MESSAGE_TIMEOUT_S,
68+
TimeUnit.SECONDS);
7569

7670
getTask()
7771
.addOnCompleteListener(
@@ -127,6 +121,7 @@ void finish() {
127121
this.scheduledExecutorService = scheduledExecutorService;
128122
}
129123

124+
@CanIgnoreReturnValue
130125
synchronized Task<Void> sendIntent(Intent intent) {
131126
if (Log.isLoggable(TAG, Log.DEBUG)) {
132127
Log.d(TAG, "new intent queued in the bind-strategy delivery");

firebase-messaging/src/test/java/com/google/firebase/messaging/FcmBroadcastProcessorRoboTest.java

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
import static org.mockito.Mockito.never;
1919
import static org.mockito.Mockito.verify;
2020
import static org.mockito.Mockito.when;
21-
import static org.robolectric.Shadows.shadowOf;
2221

2322
import android.app.Application;
2423
import android.content.Context;
2524
import android.content.Intent;
2625
import android.os.Build.VERSION_CODES;
2726
import androidx.test.core.app.ApplicationProvider;
2827
import com.google.android.gms.tasks.Task;
28+
import com.google.android.gms.tasks.TaskCompletionSource;
2929
import com.google.firebase.messaging.testing.FakeScheduledExecutorService;
3030
import org.junit.After;
3131
import org.junit.Before;
@@ -37,6 +37,7 @@
3737
import org.mockito.junit.MockitoRule;
3838
import org.robolectric.RobolectricTestRunner;
3939
import org.robolectric.annotation.Config;
40+
import org.robolectric.shadows.ShadowLooper;
4041
import org.robolectric.shadows.ShadowPowerManager;
4142

4243
/** Robolectric test for FcmBroadcastProcessor. */
@@ -51,13 +52,18 @@ public class FcmBroadcastProcessorRoboTest {
5152
private FcmBroadcastProcessor processor;
5253
private FakeScheduledExecutorService fakeExecutorService;
5354
@Mock private ServiceStarter serviceStarter;
55+
@Mock private WithinAppServiceConnection mockConnection;
56+
private TaskCompletionSource<Void> sendIntentTask;
5457

5558
@Before
5659
public void setUp() {
5760
context = ApplicationProvider.getApplicationContext();
5861
ServiceStarter.setForTesting(serviceStarter);
5962
fakeExecutorService = new FakeScheduledExecutorService();
6063
processor = new FcmBroadcastProcessor(context, fakeExecutorService);
64+
FcmBroadcastProcessor.setServiceConnection(mockConnection);
65+
sendIntentTask = new TaskCompletionSource<>();
66+
when(mockConnection.sendIntent(any(Intent.class))).thenReturn(sendIntentTask.getTask());
6167
}
6268

6369
@After
@@ -70,37 +76,82 @@ public void resetStaticState() {
7076

7177
@Test
7278
@Config(sdk = VERSION_CODES.O)
73-
public void testStartMessagingService_NormalPriorityBackgroundCheck() {
74-
// Subject to background check when run on Android O and targetSdkVersion set to O.
75-
context.getApplicationInfo().targetSdkVersion = VERSION_CODES.O;
76-
when(serviceStarter.hasWakeLockPermission(any(Context.class))).thenReturn(true);
79+
public void testStartMessagingService_Background() {
80+
setSubjectToBackgroundCheck();
81+
setWakeLockPermission(true);
82+
Intent intent = new Intent(ACTION_FCM_MESSAGE);
7783

78-
Task<Integer> startServiceTask =
79-
processor.startMessagingService(context, new Intent(ACTION_FCM_MESSAGE));
84+
Task<Integer> startServiceTask = processor.startMessagingService(context, intent);
8085

81-
// Should return immediately with SUCCESS, bind to the Service, and acquire a WakeLock.
82-
assertThat(startServiceTask.getResult()).isEqualTo(ServiceStarter.SUCCESS);
86+
// Should send the intent through the connection and not acquire a WakeLock.
87+
assertThat(startServiceTask.isComplete()).isFalse();
8388
verify(serviceStarter, never()).startMessagingService(any(), any());
84-
assertThat(shadowOf(context).getBoundServiceConnections()).hasSize(1);
89+
verify(mockConnection).sendIntent(intent);
90+
assertThat(ShadowPowerManager.getLatestWakeLock()).isNull();
91+
92+
// After the message has been handled, the task should be completed successfully.
93+
sendIntentTask.setResult(null);
94+
ShadowLooper.idleMainLooper();
95+
96+
assertThat(startServiceTask.getResult()).isEqualTo(ServiceStarter.SUCCESS);
97+
}
98+
99+
@Test
100+
@Config(sdk = VERSION_CODES.O)
101+
public void testStartMessagingService_ForegroundBindWithWakeLock() {
102+
setWakeLockPermission(true);
103+
setStartServiceFails();
104+
Intent intent = new Intent(ACTION_FCM_MESSAGE);
105+
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
106+
107+
Task<Integer> startServiceTask = processor.startMessagingService(context, intent);
108+
109+
// Should return immediately with SUCCESS, bind to the Service, and acquire a WakeLock.
110+
fakeExecutorService.runAll();
111+
assertThat(startServiceTask.isComplete()).isTrue();
112+
assertThat(startServiceTask.getResult())
113+
.isEqualTo(ServiceStarter.ERROR_ILLEGAL_STATE_EXCEPTION_FALLBACK_TO_BIND);
114+
verify(mockConnection).sendIntent(any(Intent.class));
85115
assertThat(ShadowPowerManager.getLatestWakeLock()).isNotNull();
86116
assertThat(ShadowPowerManager.getLatestWakeLock().isHeld()).isTrue();
117+
118+
// After the message has been handled, the WakeLock should be released.
119+
sendIntentTask.setResult(null);
120+
ShadowLooper.idleMainLooper();
121+
122+
assertThat(ShadowPowerManager.getLatestWakeLock().isHeld()).isFalse();
87123
}
88124

89125
@Test
90126
@Config(sdk = VERSION_CODES.O)
91-
public void testStartMessagingService_bindNoWakeLockPermission() {
127+
public void testStartMessagingService_ForegroundBindNoWakeLock() {
128+
setWakeLockPermission(false);
129+
setStartServiceFails();
130+
Intent intent = new Intent(ACTION_FCM_MESSAGE);
131+
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
132+
133+
Task<Integer> startServiceTask = processor.startMessagingService(context, intent);
134+
135+
// Should return immediately with SUCCESS, bind to the Service, and not acquire a WakeLock.
136+
fakeExecutorService.runAll();
137+
assertThat(startServiceTask.isComplete()).isTrue();
138+
assertThat(startServiceTask.getResult())
139+
.isEqualTo(ServiceStarter.ERROR_ILLEGAL_STATE_EXCEPTION_FALLBACK_TO_BIND);
140+
verify(mockConnection).sendIntent(any(Intent.class));
141+
assertThat(ShadowPowerManager.getLatestWakeLock()).isNull();
142+
}
143+
144+
private void setSubjectToBackgroundCheck() {
92145
// Subject to background check when run on Android O and targetSdkVersion set to O.
93146
context.getApplicationInfo().targetSdkVersion = VERSION_CODES.O;
94-
when(serviceStarter.hasWakeLockPermission(any(Context.class))).thenReturn(false);
147+
}
95148

96-
Task<Integer> startServiceTask =
97-
processor.startMessagingService(context, new Intent(ACTION_FCM_MESSAGE));
149+
private void setWakeLockPermission(boolean permission) {
150+
when(serviceStarter.hasWakeLockPermission(any(Context.class))).thenReturn(permission);
151+
}
98152

99-
// Should return immediately with SUCCESS and bind to the Service, but not try to acquire a
100-
// WakeLock since it doesn't hold the permission.
101-
assertThat(startServiceTask.getResult()).isEqualTo(ServiceStarter.SUCCESS);
102-
verify(serviceStarter, never()).startMessagingService(any(), any());
103-
assertThat(shadowOf(context).getBoundServiceConnections()).hasSize(1);
104-
assertThat(ShadowPowerManager.getLatestWakeLock()).isNull();
153+
private void setStartServiceFails() {
154+
when(serviceStarter.startMessagingService(any(Context.class), any(Intent.class)))
155+
.thenReturn(ServiceStarter.ERROR_ILLEGAL_STATE_EXCEPTION);
105156
}
106157
}

0 commit comments

Comments
 (0)