@onekeyfe/react-native-jpush 6.7.0-alpha.435 → 6.7.0-alpha.444

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,684 @@
1
+ package com.onekey.jpush.fcm;
2
+
3
+ import static org.junit.Assert.*;
4
+ import static org.mockito.ArgumentMatchers.*;
5
+ import static org.mockito.Mockito.*;
6
+
7
+ import android.app.ActivityManager;
8
+ import android.app.KeyguardManager;
9
+ import android.app.Notification;
10
+ import android.app.NotificationManager;
11
+ import android.content.Context;
12
+ import android.content.Intent;
13
+ import android.content.pm.ActivityInfo;
14
+ import android.content.pm.ApplicationInfo;
15
+ import android.content.pm.ResolveInfo;
16
+ import android.os.Bundle;
17
+ import android.os.Looper;
18
+ import android.os.Process;
19
+ import cn.jiguang.api.JCoreManager;
20
+ import cn.jiguang.api.utils.JCollectionAuth;
21
+ import cn.jiguang.core.JCoreCrypto;
22
+ import cn.jiguang.core.JCoreStorage;
23
+ import cn.jiguang.core.JCoreTcpEngine;
24
+ import cn.jiguang.core.JCoreWireCodec;
25
+ import cn.jiguang.plugins.push.common.JLogger;
26
+ import cn.jpush.android.api.CustomMessage;
27
+ import cn.jpush.android.api.JPushInterface;
28
+ import cn.jpush.android.api.JThirdPlatFormInterface;
29
+ import cn.jpush.android.api.NotificationMessage;
30
+ import cn.jpush.android.local.ActionHelper;
31
+ import cn.jpush.android.service.JNotifyActivity;
32
+ import cn.jpush.android.service.JPushMessageReceiver;
33
+ import com.google.android.gms.common.GoogleApiAvailabilityLight;
34
+ import com.google.android.gms.tasks.TaskCompletionSource;
35
+ import com.google.android.gms.tasks.Tasks;
36
+ import com.google.firebase.FirebaseApp;
37
+ import com.google.firebase.FirebaseOptions;
38
+ import com.google.firebase.messaging.FirebaseMessaging;
39
+ import com.google.firebase.messaging.FirebaseMessagingService;
40
+ import com.google.firebase.messaging.RemoteMessage;
41
+ import java.io.IOException;
42
+ import java.nio.charset.StandardCharsets;
43
+ import java.util.ArrayList;
44
+ import java.util.Collections;
45
+ import java.util.List;
46
+ import java.util.Map;
47
+ import org.junit.After;
48
+ import org.junit.Before;
49
+ import org.junit.Test;
50
+ import org.junit.runner.RunWith;
51
+ import org.mockito.ArgumentCaptor;
52
+ import org.mockito.MockedStatic;
53
+ import org.robolectric.Robolectric;
54
+ import org.robolectric.RobolectricTestRunner;
55
+ import org.robolectric.RuntimeEnvironment;
56
+ import org.robolectric.Shadows;
57
+ import org.robolectric.annotation.Config;
58
+ import org.robolectric.annotation.LooperMode;
59
+ import org.robolectric.util.ReflectionHelpers;
60
+
61
+ @RunWith(RobolectricTestRunner.class)
62
+ @Config(sdk = 28, manifest = Config.NONE)
63
+ @LooperMode(LooperMode.Mode.PAUSED)
64
+ public class FcmAdapterTest {
65
+ private Context context;
66
+ private FcmMessagingService service;
67
+ private NotificationManager notifications;
68
+ private Object firebaseRpc;
69
+ private final List<NotificationMessage> arrived = new ArrayList<>();
70
+ private final List<NotificationMessage> opened = new ArrayList<>();
71
+ private final List<CustomMessage> custom = new ArrayList<>();
72
+
73
+ @Before
74
+ public void setUp() throws Exception {
75
+ context = RuntimeEnvironment.getApplication();
76
+ ReflectionHelpers.setStaticField(JCoreStorage.class, "sAppContext", null);
77
+ ReflectionHelpers.<Map<String, Object>>getStaticField(JCoreStorage.class, "MEMORY_FALLBACK").clear();
78
+ JCoreStorage.setAppContext(context);
79
+ JCoreStorage.setCollectionAuth(context, true);
80
+ JCoreStorage.setPushStopped(context, false);
81
+ ReflectionHelpers.setStaticField(JThirdPlatFormInterface.class, "fcmPlugin", null);
82
+ ReflectionHelpers.setStaticField(JThirdPlatFormInterface.class, "fcmRegistered", false);
83
+ ReflectionHelpers.<java.util.Queue<String>>getStaticField(FirebaseMessagingService.class, "recentlyReceivedMessageIds").clear();
84
+ JLogger.setLoggerEnable(true);
85
+ JLogger.clearRecentLogs();
86
+ notifications = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
87
+ notifications.cancelAll();
88
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
89
+ ActionHelper.getInstance().clearRegisteredReceivers();
90
+ ActionHelper.getInstance().registerReceiver(new JPushMessageReceiver() {
91
+ @Override public void onNotifyMessageArrived(Context c, NotificationMessage message) { arrived.add(message); }
92
+ @Override public void onNotifyMessageOpened(Context c, NotificationMessage message) { opened.add(message); }
93
+ @Override public void onMessage(Context c, CustomMessage message) { custom.add(message); }
94
+ });
95
+ service = Robolectric.buildService(FcmMessagingService.class).create().get();
96
+ // Isolate only the SDK's transport acknowledgement; run its real receive/display code.
97
+ firebaseRpc = mock(Class.forName("com.google.android.gms.cloudmessaging.Rpc"));
98
+ ReflectionHelpers.setField(service, "rpc", firebaseRpc);
99
+ }
100
+
101
+ @After
102
+ public void tearDown() {
103
+ ActionHelper.getInstance().clearRegisteredReceivers();
104
+ notifications.cancelAll();
105
+ }
106
+
107
+ @Test
108
+ public void coreDiscoversSourceAdapterOnceAndRebindsTheCurrentToken() {
109
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
110
+ when(provider.getToken()).thenReturn(Tasks.forResult("current_secret_token"));
111
+ GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
112
+ when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
113
+ ActionHelper dispatcher = mock(ActionHelper.class);
114
+ JCoreTcpEngine engine = synchronousEngine();
115
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
116
+ MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
117
+ MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
118
+ MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class);
119
+ MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
120
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
121
+ google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
122
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
123
+ tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
124
+ JThirdPlatFormInterface.initializeFcm(context);
125
+ JThirdPlatFormInterface.initializeFcm(context);
126
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
127
+ app.verify(() -> FirebaseApp.initializeApp(context), times(2));
128
+ verify(provider, times(2)).getToken();
129
+ ArgumentCaptor<Bundle> actions = ArgumentCaptor.forClass(Bundle.class);
130
+ verify(dispatcher, times(2)).doAction(eq(context), eq("third_action"), actions.capture());
131
+ for (Bundle action : actions.getAllValues()) {
132
+ assertEquals("current_secret_token", action.getString("token"));
133
+ assertEquals((byte) 8, action.getByte("platform"));
134
+ }
135
+ assertLogsExclude("current_secret_token");
136
+ }
137
+ }
138
+
139
+ @Test
140
+ public void missingFirebaseConfigurationDoesNotEscapeCoreInitialization() {
141
+ GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
142
+ when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
143
+ JCoreTcpEngine engine = synchronousEngine();
144
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
145
+ MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
146
+ MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
147
+ MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
148
+ firebase.when(FirebaseMessaging::getInstance).thenThrow(new IllegalStateException("private_configuration_details"));
149
+ google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
150
+ tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
151
+ JThirdPlatFormInterface.initializeFcm(context);
152
+ assertTrue(JLogger.getRecentLogs().toString().contains("FCM registration unavailable"));
153
+ assertLogsExclude("private_configuration_details");
154
+ }
155
+ }
156
+
157
+ @Test
158
+ public void authorizationGrantedAfterSkippedInitRequestsTokenWithoutReconnect() {
159
+ JCoreStorage.setCollectionAuth(context, false);
160
+ JCoreStorage.setConnected(context, true);
161
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
162
+ when(provider.getToken()).thenReturn(Tasks.forResult("consented_secret_token"));
163
+ GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
164
+ when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
165
+ ActionHelper dispatcher = mock(ActionHelper.class);
166
+ JCoreTcpEngine engine = synchronousEngine();
167
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
168
+ MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
169
+ MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
170
+ MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class);
171
+ MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class);
172
+ MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class)) {
173
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
174
+ google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
175
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
176
+ tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
177
+ JPushInterface.init(context);
178
+ verifyNoInteractions(provider);
179
+ JCollectionAuth.setAuth(context, true);
180
+ JCollectionAuth.setAuth(context, true);
181
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
182
+ verify(provider).getToken();
183
+ assertEquals("consented_secret_token", tokenAction(dispatcher).getString("token"));
184
+ assertTrue(JCoreStorage.isConnected(context));
185
+ core.verify(() -> JCoreManager.init(context));
186
+ assertLogsExclude("consented_secret_token");
187
+ }
188
+ }
189
+
190
+ @Test
191
+ public void grantingAuthorizationWhileStoppedDoesNotRequestRegistration() {
192
+ JCoreStorage.setCollectionAuth(context, false);
193
+ JCoreStorage.setPushStopped(context, true);
194
+ try (MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
195
+ JCollectionAuth.setAuth(context, true);
196
+ assertTrue(JCollectionAuth.isAuth(context));
197
+ tcp.verifyNoInteractions();
198
+ }
199
+ }
200
+
201
+ @Test
202
+ public void revokedAuthorizationCancelsQueuedFcmRegistration() {
203
+ JCoreStorage.setCollectionAuth(context, false);
204
+ JCoreTcpEngine engine = mock(JCoreTcpEngine.class);
205
+ try (MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class);
206
+ MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class)) {
207
+ tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
208
+ JCollectionAuth.setAuth(context, true);
209
+ ArgumentCaptor<Runnable> work = ArgumentCaptor.forClass(Runnable.class);
210
+ verify(engine).executeAsync(work.capture());
211
+ JCollectionAuth.setAuth(context, false);
212
+ work.getValue().run();
213
+ firebase.verifyNoInteractions();
214
+ assertFalse(JCollectionAuth.isAuth(context));
215
+ }
216
+ }
217
+
218
+ @Test
219
+ public void tokenSuccessDispatchesPlatformEightWithoutPersistingBeforeAck() {
220
+ JCoreStorage.saveThirdToken(context, (byte) 8, "previous_acknowledged_token");
221
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
222
+ TaskCompletionSource<String> request = new TaskCompletionSource<>();
223
+ when(provider.getToken()).thenReturn(request.getTask());
224
+ ActionHelper dispatcher = mock(ActionHelper.class);
225
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
226
+ MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
227
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
228
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
229
+ assertEquals("previous_acknowledged_token", new FcmPushManager().getToken(context));
230
+ verifyNoInteractions(dispatcher);
231
+ request.setResult("rotated_secret_token");
232
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
233
+ Bundle token = tokenAction(dispatcher);
234
+ assertEquals("rotated_secret_token", token.getString("token"));
235
+ assertEquals((byte) 8, token.getByte("platform"));
236
+ assertEquals("previous_acknowledged_token", JCoreStorage.getThirdToken(context, (byte) 8));
237
+ assertLogsExclude("rotated_secret_token");
238
+ }
239
+ }
240
+
241
+ @Test
242
+ public void failedTokenRequestDoesNotBindOrLogRawError() {
243
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
244
+ when(provider.getToken()).thenReturn(Tasks.forException(new IOException("secret_provider_error")));
245
+ ActionHelper dispatcher = mock(ActionHelper.class);
246
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
247
+ MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
248
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
249
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
250
+ new FcmPushManager().getToken(context);
251
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
252
+ verifyNoInteractions(dispatcher);
253
+ assertTrue(JLogger.getRecentLogs().toString().contains("IOException"));
254
+ assertLogsExclude("secret_provider_error");
255
+ }
256
+ }
257
+
258
+ @Test
259
+ public void tokenCallbackRechecksStopAndAuthorizationAfterRequest() {
260
+ for (boolean stopped : new boolean[]{true, false}) {
261
+ JCoreStorage.setPushStopped(context, false);
262
+ JCoreStorage.setCollectionAuth(context, true);
263
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
264
+ TaskCompletionSource<String> request = new TaskCompletionSource<>();
265
+ when(provider.getToken()).thenReturn(request.getTask());
266
+ ActionHelper dispatcher = mock(ActionHelper.class);
267
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
268
+ MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
269
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
270
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
271
+ new FcmPushManager().getToken(context);
272
+ JCoreStorage.setPushStopped(context, stopped);
273
+ JCoreStorage.setCollectionAuth(context, stopped);
274
+ request.setResult("late_secret_token");
275
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
276
+ verifyNoInteractions(dispatcher);
277
+ }
278
+ }
279
+ }
280
+
281
+ @Test
282
+ public void newTokenInitializesCoreAndForwardsRotation() {
283
+ JCoreStorage.setConnected(context, true);
284
+ FirebaseMessaging provider = mock(FirebaseMessaging.class);
285
+ when(provider.getToken()).thenReturn(Tasks.forResult("new_secret_token"));
286
+ GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
287
+ when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
288
+ JCoreTcpEngine engine = synchronousEngine();
289
+ try (MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class);
290
+ MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
291
+ MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
292
+ MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
293
+ MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
294
+ firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
295
+ google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
296
+ tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
297
+ service.handleIntent(new Intent("com.google.firebase.messaging.NEW_TOKEN").putExtra("token", "new_secret_token"));
298
+ Shadows.shadowOf(Looper.getMainLooper()).idle();
299
+ core.verify(() -> JCoreManager.init(context));
300
+ ArgumentCaptor<byte[]> body = ArgumentCaptor.forClass(byte[].class);
301
+ verify(engine).enqueueOrSendRequest(eq(context), eq(JCoreWireCodec.CMD_THIRD_TOKEN),
302
+ eq(JCoreWireCodec.VER_THIRD_TOKEN), anyLong(), body.capture(), anyLong(), eq(3),
303
+ any(JCoreTcpEngine.RequestCallback.class));
304
+ assertArrayEquals(JCoreWireCodec.buildThirdTokenPayload("new_secret_token", (byte) 8, false), body.getValue());
305
+ verifyNoInteractions(provider);
306
+ assertLogsExclude("new_secret_token");
307
+ }
308
+ }
309
+
310
+ @Test
311
+ public void stoppedOrUnauthorizedNewTokenDoesNotInitializeCore() {
312
+ try (MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class)) {
313
+ JCoreStorage.setPushStopped(context, true);
314
+ service.onNewToken("ignored");
315
+ JCoreStorage.setPushStopped(context, false);
316
+ JCoreStorage.setCollectionAuth(context, false);
317
+ service.onNewToken("ignored");
318
+ core.verifyNoInteractions();
319
+ }
320
+ }
321
+
322
+ @Test
323
+ public void emptyTokensAreIgnored() {
324
+ ActionHelper dispatcher = mock(ActionHelper.class);
325
+ try (MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
326
+ helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
327
+ FcmPushManager.acceptToken(context, null);
328
+ FcmPushManager.acceptToken(context, "");
329
+ verifyNoInteractions(dispatcher);
330
+ }
331
+ }
332
+
333
+ @Test
334
+ public void googlePlayAvailabilityAndNotificationDelegation() {
335
+ GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
336
+ when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0, 9);
337
+ try (MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
338
+ MockedStatic<JPushInterface> core = mockStatic(JPushInterface.class)) {
339
+ google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
340
+ FcmPushManager manager = new FcmPushManager();
341
+ assertTrue(manager.isSupport(context));
342
+ assertFalse(manager.isSupport(context));
343
+ assertFalse(manager.isSupport(null));
344
+ manager.clearNotification(context, 123);
345
+ manager.clearAllNotification(context);
346
+ core.verify(() -> JPushInterface.clearNotificationById(context, 123));
347
+ core.verify(() -> JPushInterface.clearAllNotifications(context));
348
+ }
349
+ }
350
+
351
+ @Test
352
+ public void firebaseInitializationAndApplicationMetadata() {
353
+ FirebaseApp app = mock(FirebaseApp.class);
354
+ FirebaseOptions options = new FirebaseOptions.Builder()
355
+ .setApplicationId("1:123456:android:synthetic").setApiKey("synthetic_api_key").build();
356
+ when(app.getOptions()).thenReturn(options);
357
+ try (MockedStatic<FirebaseApp> firebase = mockStatic(FirebaseApp.class)) {
358
+ firebase.when(FirebaseApp::getInstance).thenReturn(app);
359
+ FcmPushManager manager = new FcmPushManager();
360
+ manager.init(context);
361
+ manager.register(context, new Bundle());
362
+ firebase.verify(() -> FirebaseApp.initializeApp(context), times(2));
363
+ assertEquals(options.getApplicationId(), manager.getAppId(context));
364
+ assertEquals(options.getApiKey(), manager.getAppkey(context));
365
+ assertEquals((byte) 8, manager.getRomType(context));
366
+ assertFalse(manager.isNeedClearToken(context));
367
+ }
368
+ }
369
+
370
+ @Test
371
+ public void firebaseNotificationFieldsFillMetadataAndTapOpensOnce() {
372
+ Bundle payload = metadata("{\"_jmsgid_\":\"fcm_notification\",\"n_extras\":{\"route\":\"synthetic_route\"}}");
373
+ payload.putString("gcm.n.e", "1");
374
+ payload.putString("gcm.n.title", "private_title");
375
+ payload.putString("gcm.n.body", "private_body");
376
+ service.onMessageReceived(new RemoteMessage(payload));
377
+ assertEquals(1, arrived.size());
378
+ assertEquals("private_title", arrived.get(0).notificationTitle);
379
+ assertEquals("private_body", arrived.get(0).notificationContent);
380
+ assertEquals(8, arrived.get(0).platform);
381
+ List<Notification> posted = Shadows.shadowOf(notifications).getAllNotifications();
382
+ assertEquals(1, posted.size());
383
+ assertEquals("private_title", posted.get(0).extras.getString(Notification.EXTRA_TITLE));
384
+ Intent tap = Shadows.shadowOf(posted.get(0).contentIntent).getSavedIntent();
385
+ assertEquals(JNotifyActivity.class.getName(), tap.getComponent().getClassName());
386
+ Robolectric.buildActivity(JNotifyActivity.class, tap).create();
387
+ assertEquals(1, opened.size());
388
+ assertEquals("fcm_notification", opened.get(0).msgId);
389
+ assertTrue(opened.get(0).notificationExtras.contains("synthetic_route"));
390
+ assertLogsExclude("private_title", "private_body", "synthetic_route");
391
+ }
392
+
393
+ @Test
394
+ public void postedDataOnlyTapIsIgnoredAfterStopOrAuthorizationWithdrawal() {
395
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
396
+ ActivityInfo host = new ActivityInfo();
397
+ host.packageName = context.getPackageName();
398
+ host.name = context.getPackageName() + ".MainActivity";
399
+ host.applicationInfo = new ApplicationInfo(context.getApplicationInfo());
400
+ ResolveInfo resolvedHost = new ResolveInfo();
401
+ resolvedHost.activityInfo = host;
402
+ Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setPackage(context.getPackageName());
403
+ Shadows.shadowOf(context.getPackageManager()).addResolveInfoForIntent(launcher, resolvedHost);
404
+ for (boolean stopped : new boolean[]{true, false}) {
405
+ JCoreStorage.setPushStopped(context, false);
406
+ JCoreStorage.setCollectionAuth(context, true);
407
+ service.handleIntent(firebaseReceiveIntent("deferred_tap_" + stopped, false));
408
+ List<Notification> posted = Shadows.shadowOf(notifications).getAllNotifications();
409
+ assertEquals(1, posted.size());
410
+ Intent tap = Shadows.shadowOf(posted.get(0).contentIntent).getSavedIntent();
411
+ JCoreStorage.setPushStopped(context, stopped);
412
+ JCoreStorage.setCollectionAuth(context, stopped);
413
+ Robolectric.buildActivity(JNotifyActivity.class, tap).create();
414
+ assertTrue(opened.isEmpty());
415
+ assertNull(Shadows.shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity());
416
+ notifications.cancelAll();
417
+ }
418
+ }
419
+
420
+ @Test
421
+ public void compressedMetadataRetainsItsOwnTitleAndExtras() {
422
+ Bundle payload = metadata(compress("{\"msg_id\":\"zipped\",\"override_msg_id\":\"\",\"n_title\":\"metadata_title\",\"n_content\":\"metadata_body\",\"n_extras\":{\"route\":\"private_route\"}}"));
423
+ payload.putString("gcm.n.e", "1");
424
+ payload.putString("gcm.n.title", "fallback_title");
425
+ payload.putString("gcm.n.body", "fallback_body");
426
+ service.onMessageReceived(new RemoteMessage(payload));
427
+ assertEquals(1, arrived.size());
428
+ assertEquals("zipped", arrived.get(0).msgId);
429
+ assertEquals("metadata_title", arrived.get(0).notificationTitle);
430
+ assertTrue(arrived.get(0).notificationExtras.contains("private_route"));
431
+ }
432
+
433
+ @Test
434
+ public void standardFirebaseDataUsesTransportIdAndPreservesExtras() {
435
+ Bundle payload = new Bundle();
436
+ payload.putString("google.message_id", "transport_id");
437
+ payload.putString("n_title", "Synthetic notification");
438
+ payload.putString("n_content", "Synthetic body");
439
+ payload.putString("route", "synthetic_route");
440
+ service.onMessageReceived(new RemoteMessage(payload));
441
+ assertEquals(1, arrived.size());
442
+ assertEquals("transport_id", arrived.get(0).msgId);
443
+ assertTrue(arrived.get(0).notificationExtras.contains("synthetic_route"));
444
+ }
445
+
446
+ @Test
447
+ public void customDataDoesNotDisplayANotification() {
448
+ service.onMessageReceived(new RemoteMessage(metadata("{\"msg_id\":\"custom_id\",\"message\":\"private_custom_body\",\"content_type\":\"text\",\"extras\":{\"route\":\"private_custom_route\"}}")));
449
+ assertEquals(1, custom.size());
450
+ assertEquals("custom_id", custom.get(0).messageId);
451
+ assertEquals("private_custom_body", custom.get(0).message);
452
+ assertEquals("text", custom.get(0).contentType);
453
+ assertTrue(arrived.isEmpty());
454
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
455
+ assertLogsExclude("private_custom_body", "private_custom_route");
456
+ }
457
+
458
+ @Test
459
+ public void malformedAndOversizedPayloadsDoNotReachCallbacks() {
460
+ service.onMessageReceived(new RemoteMessage(metadata("{broken")));
461
+ service.onMessageReceived(new RemoteMessage(metadata("{\"jg_c\":\"not-gzip\"}")));
462
+ StringBuilder oversized = new StringBuilder();
463
+ for (int i = 0; i < 70000; i++) oversized.append('x');
464
+ service.onMessageReceived(new RemoteMessage(metadata(compress(oversized.toString()))));
465
+ assertTrue(arrived.isEmpty());
466
+ assertTrue(custom.isEmpty());
467
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
468
+ }
469
+
470
+ @Test
471
+ public void stoppedAndUnauthorizedNotificationsAreIgnored() {
472
+ String body = "{\"msg_id\":\"blocked\",\"n_content\":\"synthetic\"}";
473
+ JCoreStorage.setPushStopped(context, true);
474
+ service.onMessageReceived(new RemoteMessage(metadata(body)));
475
+ JCoreStorage.setPushStopped(context, false);
476
+ JCoreStorage.setCollectionAuth(context, false);
477
+ service.onMessageReceived(new RemoteMessage(metadata(body)));
478
+ JCoreStorage.setCollectionAuth(context, true);
479
+ assertTrue(arrived.isEmpty());
480
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
481
+ }
482
+
483
+ @Test
484
+ public void foregroundDisplayDisabledSuppressesNotificationAndArrival() {
485
+ service.onMessageReceived(foregroundSuppressedMessage());
486
+ assertTrue(arrived.isEmpty());
487
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
488
+ }
489
+
490
+ @Test
491
+ public void backgroundDataOnlyMessageStillDisplaysAndDispatchesArrival() {
492
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
493
+ RemoteMessage message = foregroundSuppressedMessage();
494
+ assertNull(message.getNotification());
495
+ service.onMessageReceived(message);
496
+ assertEquals(1, arrived.size());
497
+ assertEquals("foreground_suppressed", arrived.get(0).msgId);
498
+ assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
499
+ }
500
+
501
+ @Test
502
+ public void foregroundDisplayDecisionUsesCurrentProcessStateForEachMessage() {
503
+ service.onMessageReceived(foregroundSuppressedMessage());
504
+ assertTrue(arrived.isEmpty());
505
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
506
+ service.onMessageReceived(foregroundSuppressedMessage());
507
+ assertEquals(1, arrived.size());
508
+ notifications.cancelAll();
509
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
510
+ service.onMessageReceived(foregroundSuppressedMessage());
511
+ assertEquals(1, arrived.size());
512
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
513
+ }
514
+
515
+ @Test
516
+ public void lockedScreenDoesNotSuppressDataOnlyNotification() {
517
+ KeyguardManager keyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
518
+ Shadows.shadowOf(keyguard).setKeyguardLocked(true);
519
+ service.onMessageReceived(foregroundSuppressedMessage());
520
+ assertEquals(1, arrived.size());
521
+ assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
522
+ }
523
+
524
+ @Test
525
+ public void stoppedBackgroundCombinedIntentCannotUseFirebaseAutomaticDisplay() {
526
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
527
+ JCoreStorage.setPushStopped(context, true);
528
+ service.handleIntent(firebaseReceiveIntent("stopped_combined", true));
529
+ assertTrue(arrived.isEmpty());
530
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
531
+ verifyNoInteractions(firebaseRpc);
532
+ assertLogsExclude("private_title", "private_body");
533
+ }
534
+
535
+ @Test
536
+ public void unauthorizedBackgroundNotificationIntentCannotUseFirebaseAutomaticDisplay() {
537
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
538
+ JCoreStorage.setCollectionAuth(context, false);
539
+ Intent notificationOnly = firebaseReceiveIntent("unauthorized_notification", true);
540
+ notificationOnly.removeExtra("JMessageExtra");
541
+ service.handleIntent(notificationOnly);
542
+ assertTrue(arrived.isEmpty());
543
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
544
+ verifyNoInteractions(firebaseRpc);
545
+ assertLogsExclude("private_title", "private_body");
546
+ }
547
+
548
+ @Test
549
+ public void authorizedBackgroundCombinedIntentKeepsFirebaseDisplayAndDeduplication() {
550
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
551
+ Intent receive = firebaseReceiveIntent("allowed_combined", true);
552
+ service.handleIntent(receive);
553
+ service.handleIntent(new Intent(receive));
554
+ assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
555
+ assertTrue(arrived.isEmpty());
556
+ assertEquals(2, mockingDetails(firebaseRpc).getInvocations().size());
557
+ }
558
+
559
+ @Test
560
+ public void authorizedBackgroundDataOnlyIntentUsesNativeDisplayAndFirebaseDeduplication() {
561
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
562
+ Intent receive = firebaseReceiveIntent("allowed_data", false);
563
+ service.handleIntent(receive);
564
+ service.handleIntent(new Intent(receive));
565
+ assertEquals(1, arrived.size());
566
+ assertEquals("allowed_data", arrived.get(0).msgId);
567
+ assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
568
+ }
569
+
570
+ @Test
571
+ public void receiveGateRechecksStateAfterStopWithdrawalAndRegrant() {
572
+ setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
573
+ JCoreStorage.setPushStopped(context, true);
574
+ service.handleIntent(firebaseReceiveIntent("stopped", true));
575
+ JCoreStorage.setPushStopped(context, false);
576
+ JCoreStorage.setCollectionAuth(context, false);
577
+ service.handleIntent(firebaseReceiveIntent("withdrawn", true));
578
+ assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
579
+ JCoreStorage.setCollectionAuth(context, true);
580
+ service.handleIntent(firebaseReceiveIntent("regranted", true));
581
+ assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
582
+ assertEquals(1, mockingDetails(firebaseRpc).getInvocations().size());
583
+ }
584
+
585
+ @Test
586
+ @Config(sdk = 34)
587
+ public void startupRevokesOldPlayServicesDelegateWithoutTokenOrConsent() {
588
+ JCoreStorage.setPushStopped(context, true);
589
+ JCoreStorage.setCollectionAuth(context, false);
590
+ notifications.setNotificationDelegate("com.google.android.gms");
591
+ try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class)) {
592
+ Robolectric.buildContentProvider(FcmInitProvider.class).create();
593
+ assertNull(notifications.getNotificationDelegate());
594
+ assertFalse(JCollectionAuth.isAuth(context));
595
+ assertTrue(JCoreStorage.isPushStopped(context));
596
+ firebase.verifyNoInteractions();
597
+ }
598
+ }
599
+
600
+ @Test
601
+ @Config(sdk = 34)
602
+ public void startupPreservesOtherNotificationDelegates() {
603
+ notifications.setNotificationDelegate("other.notification.delegate");
604
+ Robolectric.buildContentProvider(FcmInitProvider.class).create();
605
+ assertEquals("other.notification.delegate", notifications.getNotificationDelegate());
606
+ }
607
+
608
+ @Test
609
+ public void automaticFirebaseTapLaunchesHostAndKeepsCompressedMetadata() {
610
+ ActivityInfo activity = new ActivityInfo();
611
+ activity.packageName = context.getPackageName();
612
+ activity.name = context.getPackageName() + ".MainActivity";
613
+ activity.applicationInfo = new ApplicationInfo(context.getApplicationInfo());
614
+ ResolveInfo resolve = new ResolveInfo();
615
+ resolve.activityInfo = activity;
616
+ Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setPackage(context.getPackageName());
617
+ Shadows.shadowOf(context.getPackageManager()).addResolveInfoForIntent(launcher, resolve);
618
+ Intent tap = new Intent(JPushInterface.ACTION_NOTIFICATION_OPENED)
619
+ .putExtra("JMessageExtra", compress("{\"msg_id\":\"cold_tap\",\"n_extras\":{\"route\":\"synthetic_route\"}}"));
620
+ Robolectric.buildActivity(JNotifyActivity.class, tap).create();
621
+ assertEquals(1, opened.size());
622
+ assertEquals("cold_tap", opened.get(0).msgId);
623
+ Intent launched = Shadows.shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity();
624
+ assertNotNull(launched);
625
+ assertEquals(activity.name, launched.getComponent().getClassName());
626
+ assertTrue(opened.get(0).notificationExtras.contains("synthetic_route"));
627
+ }
628
+
629
+ private static Bundle metadata(String json) {
630
+ Bundle payload = new Bundle();
631
+ payload.putString("JMessageExtra", json);
632
+ return payload;
633
+ }
634
+
635
+ private static RemoteMessage foregroundSuppressedMessage() {
636
+ return new RemoteMessage(metadata("{\"msg_id\":\"foreground_suppressed\",\"n_content\":\"synthetic\",\"n_foreground\":\"0\"}"));
637
+ }
638
+
639
+ private static Intent firebaseReceiveIntent(String id, boolean notification) {
640
+ Intent receive = new Intent("com.google.android.c2dm.intent.RECEIVE")
641
+ .putExtra("google.message_id", id)
642
+ .putExtras(metadata("{\"msg_id\":\"" + id + "\",\"n_content\":\"private_body\",\"n_foreground\":\"0\"}"));
643
+ if (notification) {
644
+ receive.putExtra("gcm.n.e", "1");
645
+ receive.putExtra("gcm.n.title", "private_title");
646
+ receive.putExtra("gcm.n.body", "private_body");
647
+ receive.putExtra("gcm.n.icon", "android");
648
+ }
649
+ return receive;
650
+ }
651
+
652
+ private void setProcessImportance(int importance) {
653
+ ActivityManager.RunningAppProcessInfo process = new ActivityManager.RunningAppProcessInfo();
654
+ process.pid = Process.myPid();
655
+ process.importance = importance;
656
+ ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
657
+ Shadows.shadowOf(manager).setProcesses(Collections.singletonList(process));
658
+ }
659
+
660
+ private static String compress(String json) {
661
+ return "{\"jg_c\":\"" + JCoreCrypto.base64Encode(JCoreCrypto.gzipCompress(json.getBytes(StandardCharsets.UTF_8))) + "\"}";
662
+ }
663
+
664
+ private Bundle tokenAction(ActionHelper dispatcher) {
665
+ ArgumentCaptor<Bundle> action = ArgumentCaptor.forClass(Bundle.class);
666
+ verify(dispatcher).doAction(eq(context), eq("third_action"), action.capture());
667
+ assertEquals(JThirdPlatFormInterface.ACTION_REGISTER_TOKEN, action.getValue().getString("third_key_action"));
668
+ return action.getValue();
669
+ }
670
+
671
+ private static JCoreTcpEngine synchronousEngine() {
672
+ JCoreTcpEngine engine = mock(JCoreTcpEngine.class);
673
+ doAnswer(call -> {
674
+ call.<Runnable>getArgument(0).run();
675
+ return null;
676
+ }).when(engine).executeAsync(any(Runnable.class));
677
+ return engine;
678
+ }
679
+
680
+ private static void assertLogsExclude(String... values) {
681
+ String logs = JLogger.getRecentLogs().toString();
682
+ for (String value : values) assertFalse("Sensitive payload appeared in native logger", logs.contains(value));
683
+ }
684
+ }