Skip to content

Commit 906aaaf

Browse files
authored
Merge pull request #348 from Backendless/oleg.vyalyh/separate_fcm_gcm
BKNDLSS-17770 Separate GCM and FCM logic.
2 parents 85eb223 + 32e918b commit 906aaaf

File tree

6 files changed

+388
-192
lines changed

6 files changed

+388
-192
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ apply plugin: 'signing'
55

66

77
group 'com.backendless'
8-
version '5.2.0'
8+
version '5.2.1'
99
archivesBaseName='backendless'
1010

1111

src/com/backendless/Messaging.java

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.backendless.messaging.PublishStatusEnum;
5151
import com.backendless.messaging.PushBroadcastMask;
5252
import com.backendless.push.BackendlessPushService;
53+
import com.backendless.push.DeviceRegistrationResult;
5354
import com.backendless.push.FCMRegistration;
5455
import com.backendless.push.GCMRegistrar;
5556
import com.backendless.rt.messaging.Channel;
@@ -253,70 +254,70 @@ private synchronized void registerDeviceGCMSync( Context context, String GCMSend
253254
}
254255

255256
/**
256-
* For FireBase messaging only.
257+
* For Firebase Cloud Messaging only
257258
*/
258259
public void registerDevice()
259260
{
260-
registerDevice( (AsyncCallback<String>) null );
261+
registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ) );
261262
}
262263

263264
/**
264-
* For FireBase messaging only.
265+
* For Firebase Cloud Messaging only
265266
*/
266-
public void registerDevice( AsyncCallback<String> callback )
267+
public void registerDevice( List<String> channels )
267268
{
268-
registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), callback );
269+
registerDevice( channels, (Date) null );
269270
}
270271

271272
/**
272-
* For FireBase messaging only.
273+
* For Firebase Cloud Messaging only
273274
*/
274-
public void registerDevice( List<String> channels )
275+
public void registerDevice( List<String> channels, Date expiration )
275276
{
276-
registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), (AsyncCallback<String>) null );
277+
registerDevice( channels, expiration, (AsyncCallback<DeviceRegistrationResult>) null );
277278
}
278279

279280
/**
280-
* For FireBase messaging only.
281+
* For Firebase Cloud Messaging only
281282
*/
282-
public void registerDevice( List<String> channels, AsyncCallback<String> callback )
283+
public void registerDevice( AsyncCallback<DeviceRegistrationResult> callback )
283284
{
284-
registerDevice( channels, (Date) null, callback );
285+
registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), (Date) null, callback );
285286
}
286-
287+
287288
/**
288-
* For FireBase messaging only.
289+
* For Firebase Cloud Messaging only
289290
*/
290-
public void registerDevice( List<String> channels, Date expiration )
291+
public void registerDevice( List<String> channels, AsyncCallback<DeviceRegistrationResult> callback )
291292
{
292-
registerDevice( channels, expiration, (AsyncCallback<String>) null );
293+
registerDevice( channels, (Date) null, callback );
293294
}
294295

295296
/**
296-
* For FireBase messaging only.
297+
* For Firebase Cloud Messaging only
297298
*/
298-
public void registerDevice( List<String> channels, Date expiration, AsyncCallback<String> callback )
299+
public void registerDevice( List<String> channels, Date expiration, AsyncCallback<DeviceRegistrationResult> callback )
299300
{
300301
if( !BackendlessPushService.isFCM( ContextHandler.getAppContext() ) )
301302
throw new IllegalStateException( "The method is intended only for FireBase messaging." );
302303

303304
if( channels == null || channels.isEmpty() ||
304305
(channels.size() == 1 && (channels.get( 0 ) == null || channels.get( 0 ).isEmpty())) )
305-
{
306306
channels = Collections.singletonList( DEFAULT_CHANNEL_NAME );
307-
}
308307

309308
for( String channel : channels )
310309
checkChannelName( channel );
311310

312311
long expirationMs = 0;
312+
313313
if( expiration != null)
314314
{
315315
if( expiration.before( Calendar.getInstance().getTime() ) )
316316
throw new IllegalArgumentException( ExceptionMessage.WRONG_EXPIRATION_DATE );
317317
else
318318
expirationMs = expiration.getTime();
319319
}
320+
320321
FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, callback );
321322
}
322323

@@ -397,12 +398,12 @@ public void unregisterDevice( List<String> channels )
397398
unregisterDevice( channels, null );
398399
}
399400

400-
public void unregisterDevice( AsyncCallback<Void> callback )
401+
public void unregisterDevice( AsyncCallback<Integer> callback )
401402
{
402403
unregisterDevice( null, callback );
403404
}
404405

405-
public void unregisterDevice( final List<String> channels, final AsyncCallback<Void> callback )
406+
public void unregisterDevice( final List<String> channels, final AsyncCallback<Integer> callback )
406407
{
407408
new AsyncTask<Void, Void, RuntimeException>()
408409
{
@@ -415,7 +416,7 @@ protected RuntimeException doInBackground( Void... params )
415416

416417
if ( BackendlessPushService.isFCM( context ) )
417418
{
418-
FCMRegistration.unregisterDevice( context, channels );
419+
FCMRegistration.unregisterDevice( context, channels, callback );
419420
}
420421
else
421422
{
@@ -442,11 +443,6 @@ protected void onPostExecute( RuntimeException result )
442443

443444
callback.handleFault( new BackendlessFault( result ) );
444445
}
445-
else
446-
{
447-
if( callback != null )
448-
callback.handleResponse( null );
449-
}
450446
}
451447
}.execute();
452448
}
Lines changed: 199 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,80 @@
11
package com.backendless.push;
22

3+
import android.app.Notification;
4+
import android.app.NotificationChannel;
5+
import android.app.NotificationManager;
6+
import android.app.PendingIntent;
7+
import android.content.Context;
38
import android.content.Intent;
9+
import android.media.AudioAttributes;
10+
import android.media.AudioManager;
11+
import android.os.Handler;
12+
import android.os.Looper;
13+
import android.support.v4.app.NotificationCompat;
14+
import android.support.v4.app.NotificationManagerCompat;
415
import android.util.Log;
16+
import com.backendless.Backendless;
17+
import com.backendless.ContextHandler;
18+
import com.backendless.async.callback.AsyncCallback;
19+
import com.backendless.exceptions.BackendlessFault;
20+
import com.backendless.messaging.AndroidPushTemplate;
21+
import com.backendless.messaging.PublishOptions;
522
import com.google.firebase.messaging.FirebaseMessagingService;
623
import com.google.firebase.messaging.RemoteMessage;
724

25+
import java.util.concurrent.atomic.AtomicInteger;
26+
827

928
public class BackendlessFCMService extends FirebaseMessagingService
1029
{
30+
private static final String IMMEDIATE_MESSAGE = "ImmediateMessage";
1131
private static final String TAG = BackendlessFCMService.class.getSimpleName();
32+
private static AtomicInteger notificationIdGenerator;
33+
34+
/**
35+
* <p>This method is intended for overriding.
36+
* <p>The notification payload can be found within intent extras: intent.getStringExtra(PublishOptions.<CONSTANT_VALUE>).
37+
* @param appContext Application context of current android app.
38+
* @param msgIntent Contains all notification data.
39+
* @return Return 'true' if you want backendless library to continue processing, 'false' otherwise.
40+
*/
41+
public boolean onMessage( Context appContext, Intent msgIntent )
42+
{
43+
Log.i( TAG, "Notification has been received by default 'BackendlessFCMService' class. You may override this method in a custom fcm service class which extends from 'com.backendless.push.BackendlessFCMService'. The notification payload can be found within intent extras: msgIntent.getStringExtra(PublishOptions.<CONSTANT_VALUE>)." );
44+
return true;
45+
}
46+
47+
public BackendlessFCMService()
48+
{
49+
if( BackendlessFCMService.notificationIdGenerator == null )
50+
BackendlessFCMService.notificationIdGenerator = new AtomicInteger( Backendless.getNotificationIdGeneratorInitValue() );
51+
}
52+
53+
@Override
54+
public final void onDestroy()
55+
{
56+
super.onDestroy();
57+
Backendless.saveNotificationIdGeneratorState( BackendlessFCMService.notificationIdGenerator.get() );
58+
}
1259

1360
@Override
1461
public final void onNewToken( String token )
1562
{
1663
super.onNewToken( token );
17-
Intent msgWork = new Intent( BackendlessPushService.ACTION_FCM_REFRESH_TOKEN );
18-
msgWork.putExtra( BackendlessPushService.KEY_DEVICE_TOKEN, token );
19-
BackendlessPushService.enqueueWork( this.getApplicationContext(), msgWork );
64+
Context appContext = ContextHandler.getAppContext();
65+
this.refreshTokenOnBackendless( appContext, token );
2066
}
2167

2268
@Override
2369
public final void onMessageReceived( RemoteMessage remoteMessage )
2470
{
25-
Intent msgWork = remoteMessage.toIntent();
26-
msgWork.setAction( BackendlessPushService.ACTION_FCM_ONMESSAGE );
27-
BackendlessPushService.enqueueWork( this.getApplicationContext(), msgWork );
71+
Intent msgIntent = remoteMessage.toIntent();
72+
Context appContext = ContextHandler.getAppContext();
73+
74+
boolean showPushNotification = this.onMessage( appContext, msgIntent );
75+
76+
if( showPushNotification )
77+
this.handleMessage( appContext, msgIntent );
2878
}
2979

3080
@Override
@@ -33,4 +83,147 @@ public void onDeletedMessages()
3383
super.onDeletedMessages();
3484
Log.w( TAG, "there are too many messages (>100) pending for this app or your device hasn't connected to FCM in more than one month." );
3585
}
86+
87+
private void handleMessage( final Context context, Intent intent )
88+
{
89+
int notificationId = BackendlessFCMService.notificationIdGenerator.getAndIncrement();
90+
91+
try
92+
{
93+
String immediatePush = intent.getStringExtra( PublishOptions.ANDROID_IMMEDIATE_PUSH );
94+
if( immediatePush != null )
95+
{
96+
AndroidPushTemplate androidPushTemplate = (AndroidPushTemplate) weborb.util.io.Serializer.fromBytes( immediatePush.getBytes(), weborb.util.io.Serializer.JSON, false );
97+
98+
if( androidPushTemplate.getContentAvailable() != null && androidPushTemplate.getContentAvailable() == 1 )
99+
return;
100+
101+
if( androidPushTemplate.getName() == null || androidPushTemplate.getName().isEmpty() )
102+
androidPushTemplate.setName( BackendlessFCMService.IMMEDIATE_MESSAGE );
103+
104+
if( android.os.Build.VERSION.SDK_INT > 25 )
105+
{
106+
if( PushTemplateHelper.getNotificationChannel( context, androidPushTemplate.getName() ) == null )
107+
androidPushTemplate.setName( BackendlessFCMService.IMMEDIATE_MESSAGE );
108+
}
109+
110+
Notification notification = PushTemplateHelper.convertFromTemplate( context, androidPushTemplate, intent.getExtras().deepCopy(), notificationId );
111+
PushTemplateHelper.showNotification( context, notification, androidPushTemplate.getName(), notificationId );
112+
return;
113+
}
114+
115+
final String templateName = intent.getStringExtra( PublishOptions.TEMPLATE_NAME );
116+
if( templateName != null )
117+
{
118+
AndroidPushTemplate androidPushTemplate = PushTemplateHelper.getPushNotificationTemplates().get( templateName );
119+
if( androidPushTemplate != null )
120+
{
121+
if( androidPushTemplate.getContentAvailable() != null && androidPushTemplate.getContentAvailable() == 1 )
122+
return;
123+
124+
Notification notification = PushTemplateHelper.convertFromTemplate( context, androidPushTemplate, intent.getExtras().deepCopy(), notificationId );
125+
intent.getExtras().deepCopy();
126+
PushTemplateHelper.showNotification( context, notification, androidPushTemplate.getName(), notificationId );
127+
}
128+
return;
129+
}
130+
131+
132+
final String message = intent.getStringExtra( PublishOptions.MESSAGE_TAG );
133+
final String contentTitle = intent.getStringExtra( PublishOptions.ANDROID_CONTENT_TITLE_TAG );
134+
final String summarySubText = intent.getStringExtra( PublishOptions.ANDROID_SUMMARY_SUBTEXT_TAG );
135+
String soundResource = intent.getStringExtra( PublishOptions.ANDROID_CONTENT_SOUND_TAG );
136+
fallBackMode( context, message, contentTitle, summarySubText, soundResource, notificationId );
137+
}
138+
catch ( Throwable throwable )
139+
{
140+
Log.e( TAG, "Error processing push notification", throwable );
141+
}
142+
}
143+
144+
private void fallBackMode( Context context, String message, String contentTitle, String summarySubText, String soundResource, final int notificationId )
145+
{
146+
final String channelName = "Fallback";
147+
final NotificationCompat.Builder notificationBuilder;
148+
149+
// android.os.Build.VERSION_CODES.O == 26
150+
if( android.os.Build.VERSION.SDK_INT > 25 )
151+
{
152+
final String channelId = Backendless.getApplicationId() + ":" + channelName;
153+
NotificationManager notificationManager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE );
154+
NotificationChannel notificationChannel = notificationManager.getNotificationChannel( channelId );
155+
156+
if( notificationChannel == null )
157+
{
158+
notificationChannel = new NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT );
159+
160+
AudioAttributes audioAttributes = new AudioAttributes.Builder()
161+
.setUsage( AudioAttributes.USAGE_NOTIFICATION_RINGTONE )
162+
.setContentType( AudioAttributes.CONTENT_TYPE_SONIFICATION )
163+
.setFlags( AudioAttributes.FLAG_AUDIBILITY_ENFORCED )
164+
.setLegacyStreamType( AudioManager.STREAM_NOTIFICATION )
165+
.build();
166+
167+
notificationChannel.setSound( PushTemplateHelper.getSoundUri( context, soundResource ), audioAttributes );
168+
notificationManager.createNotificationChannel( notificationChannel );
169+
}
170+
171+
notificationBuilder = new NotificationCompat.Builder( context, notificationChannel.getId() );
172+
}
173+
else
174+
notificationBuilder = new NotificationCompat.Builder( context );
175+
176+
notificationBuilder.setSound( PushTemplateHelper.getSoundUri( context, soundResource ), AudioManager.STREAM_NOTIFICATION );
177+
178+
int appIcon = context.getApplicationInfo().icon;
179+
if( appIcon == 0 )
180+
appIcon = android.R.drawable.sym_def_app_icon;
181+
182+
Intent notificationIntent = context.getPackageManager().getLaunchIntentForPackage( context.getApplicationInfo().packageName );
183+
PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationIntent, 0 );
184+
185+
notificationBuilder.setContentIntent( contentIntent )
186+
.setSmallIcon( appIcon )
187+
.setContentTitle( contentTitle )
188+
.setSubText( summarySubText )
189+
.setContentText( message )
190+
.setWhen( System.currentTimeMillis() )
191+
.setAutoCancel( true )
192+
.build();
193+
194+
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from( context );
195+
Handler handler = new Handler( Looper.getMainLooper() );
196+
handler.post( new Runnable()
197+
{
198+
@Override
199+
public void run()
200+
{
201+
notificationManager.notify( channelName, notificationId, notificationBuilder.build() );
202+
}
203+
} );
204+
}
205+
206+
private void refreshTokenOnBackendless( final Context context, String newDeviceToken )
207+
{
208+
Backendless.Messaging.refreshDeviceToken( newDeviceToken, new AsyncCallback<Boolean>()
209+
{
210+
@Override
211+
public void handleResponse( Boolean response )
212+
{
213+
if( response )
214+
Log.d( TAG, "Device token refreshed successfully." );
215+
else
216+
{
217+
Log.d( TAG, "Device is not registered on any channel." );
218+
FCMRegistration.unregisterDeviceOnFCM( context, null );
219+
}
220+
}
221+
222+
@Override
223+
public void handleFault( BackendlessFault fault )
224+
{
225+
Log.e( TAG, "Can not refresh device token on Backendless. " + fault.getMessage() );
226+
}
227+
} );
228+
}
36229
}

0 commit comments

Comments
 (0)