From 0e10ffc81de5dad8bb9b1dca6e021f7286d7eb2d Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 8 Nov 2018 15:48:46 +0200 Subject: [PATCH 1/6] BKNDLSS-17770 Separate GCM and FCM logic. --- build.gradle | 2 +- src/com/backendless/Messaging.java | 38 ++-- .../push/BackendlessFCMService.java | 205 +++++++++++++++++- .../push/BackendlessPushService.java | 139 +----------- src/com/backendless/push/FCMRegistration.java | 128 +++++++++-- 5 files changed, 325 insertions(+), 187 deletions(-) diff --git a/build.gradle b/build.gradle index 43d1b87dd..9e11d5b04 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'signing' group 'com.backendless' -version '5.2.0' +version '5.2.1' archivesBaseName='backendless' diff --git a/src/com/backendless/Messaging.java b/src/com/backendless/Messaging.java index 17c6f4312..b143d0239 100644 --- a/src/com/backendless/Messaging.java +++ b/src/com/backendless/Messaging.java @@ -62,6 +62,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; public final class Messaging @@ -257,15 +258,7 @@ private synchronized void registerDeviceGCMSync( Context context, String GCMSend */ public void registerDevice() { - registerDevice( (AsyncCallback) null ); - } - - /** - * For FireBase messaging only. - */ - public void registerDevice( AsyncCallback callback ) - { - registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), callback ); + registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ) ); } /** @@ -273,29 +266,31 @@ public void registerDevice( AsyncCallback callback ) */ public void registerDevice( List channels ) { - registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), (AsyncCallback) null ); + registerDevice( channels, (Date) null ); } /** * For FireBase messaging only. */ - public void registerDevice( List channels, AsyncCallback callback ) + public void registerDevice( List channels, Date expiration ) { - registerDevice( channels, (Date) null, callback ); + registerDevice( channels, expiration, (AsyncCallback) null, (AsyncCallback>)null ); } /** * For FireBase messaging only. */ - public void registerDevice( List channels, Date expiration ) + public void registerDevice( List channels, AsyncCallback fcmCallback, AsyncCallback> bkndlsCallback ) { - registerDevice( channels, expiration, (AsyncCallback) null ); + registerDevice( channels, (Date) null, fcmCallback, bkndlsCallback ); } /** * For FireBase messaging only. + * @param fcmCallback Triggered on successful/error event during registration on Google FCM. On success receives deviceToken. + * @param bkndlsCallback Triggered on successful/error event during registration on Backendless server. On success receive channelRegistrations map, where key is a channel name and value is a device registration id (table DeviceRegistrations). */ - public void registerDevice( List channels, Date expiration, AsyncCallback callback ) + public void registerDevice( List channels, Date expiration, AsyncCallback fcmCallback, AsyncCallback> bkndlsCallback ) { if( !BackendlessPushService.isFCM( ContextHandler.getAppContext() ) ) throw new IllegalStateException( "The method is intended only for FireBase messaging." ); @@ -317,7 +312,7 @@ public void registerDevice( List channels, Date expiration, AsyncCallbac else expirationMs = expiration.getTime(); } - FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, callback ); + FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, fcmCallback, bkndlsCallback ); } private void checkChannelName( String channelName ) @@ -397,12 +392,12 @@ public void unregisterDevice( List channels ) unregisterDevice( channels, null ); } - public void unregisterDevice( AsyncCallback callback ) + public void unregisterDevice( AsyncCallback callback ) { unregisterDevice( null, callback ); } - public void unregisterDevice( final List channels, final AsyncCallback callback ) + public void unregisterDevice( final List channels, final AsyncCallback callback ) { new AsyncTask() { @@ -415,7 +410,7 @@ protected RuntimeException doInBackground( Void... params ) if ( BackendlessPushService.isFCM( context ) ) { - FCMRegistration.unregisterDevice( context, channels ); + FCMRegistration.unregisterDevice( context, channels, callback ); } else { @@ -442,11 +437,6 @@ protected void onPostExecute( RuntimeException result ) callback.handleFault( new BackendlessFault( result ) ); } - else - { - if( callback != null ) - callback.handleResponse( null ); - } } }.execute(); } diff --git a/src/com/backendless/push/BackendlessFCMService.java b/src/com/backendless/push/BackendlessFCMService.java index 962cea1e9..57ef05b9d 100644 --- a/src/com/backendless/push/BackendlessFCMService.java +++ b/src/com/backendless/push/BackendlessFCMService.java @@ -1,30 +1,80 @@ package com.backendless.push; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Looper; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; import android.util.Log; +import com.backendless.Backendless; +import com.backendless.ContextHandler; +import com.backendless.async.callback.AsyncCallback; +import com.backendless.exceptions.BackendlessFault; +import com.backendless.messaging.AndroidPushTemplate; +import com.backendless.messaging.PublishOptions; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; +import java.util.concurrent.atomic.AtomicInteger; + public class BackendlessFCMService extends FirebaseMessagingService { + private static final String IMMEDIATE_MESSAGE = "ImmediateMessage"; private static final String TAG = BackendlessFCMService.class.getSimpleName(); + private static AtomicInteger notificationIdGenerator; + + /** + *

This method is intended for overriding. + *

The notification payload can be found within intent extras: intent.getStringExtra(PublishOptions.). + * @param appContext Application context of current android app. + * @param msgIntent Contains all notification data. + * @return Return 'true' if you want backendless library to continue processing, 'false' otherwise. + */ + public boolean onMessage( Context appContext, Intent msgIntent ) + { + 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.)." ); + return true; + } + + public BackendlessFCMService() + { + if( BackendlessFCMService.notificationIdGenerator == null ) + BackendlessFCMService.notificationIdGenerator = new AtomicInteger( Backendless.getNotificationIdGeneratorInitValue() ); + } + + @Override + public final void onDestroy() + { + super.onDestroy(); + Backendless.saveNotificationIdGeneratorState( BackendlessFCMService.notificationIdGenerator.get() ); + } @Override public final void onNewToken( String token ) { super.onNewToken( token ); - Intent msgWork = new Intent( BackendlessPushService.ACTION_FCM_REFRESH_TOKEN ); - msgWork.putExtra( BackendlessPushService.KEY_DEVICE_TOKEN, token ); - BackendlessPushService.enqueueWork( this.getApplicationContext(), msgWork ); + Context appContext = ContextHandler.getAppContext(); + this.refreshTokenOnBackendless( appContext, token ); } @Override public final void onMessageReceived( RemoteMessage remoteMessage ) { - Intent msgWork = remoteMessage.toIntent(); - msgWork.setAction( BackendlessPushService.ACTION_FCM_ONMESSAGE ); - BackendlessPushService.enqueueWork( this.getApplicationContext(), msgWork ); + Intent msgIntent = remoteMessage.toIntent(); + Context appContext = ContextHandler.getAppContext(); + + boolean showPushNotification = this.onMessage( appContext, msgIntent ); + + if( showPushNotification ) + this.handleMessage( appContext, msgIntent ); } @Override @@ -33,4 +83,147 @@ public void onDeletedMessages() super.onDeletedMessages(); 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." ); } + + private void handleMessage( final Context context, Intent intent ) + { + int notificationId = BackendlessFCMService.notificationIdGenerator.getAndIncrement(); + + try + { + String immediatePush = intent.getStringExtra( PublishOptions.ANDROID_IMMEDIATE_PUSH ); + if( immediatePush != null ) + { + AndroidPushTemplate androidPushTemplate = (AndroidPushTemplate) weborb.util.io.Serializer.fromBytes( immediatePush.getBytes(), weborb.util.io.Serializer.JSON, false ); + + if( androidPushTemplate.getContentAvailable() != null && androidPushTemplate.getContentAvailable() == 1 ) + return; + + if( androidPushTemplate.getName() == null || androidPushTemplate.getName().isEmpty() ) + androidPushTemplate.setName( BackendlessFCMService.IMMEDIATE_MESSAGE ); + + if( android.os.Build.VERSION.SDK_INT > 25 ) + { + if( PushTemplateHelper.getNotificationChannel( context, androidPushTemplate.getName() ) == null ) + androidPushTemplate.setName( BackendlessFCMService.IMMEDIATE_MESSAGE ); + } + + Notification notification = PushTemplateHelper.convertFromTemplate( context, androidPushTemplate, intent.getExtras().deepCopy(), notificationId ); + PushTemplateHelper.showNotification( context, notification, androidPushTemplate.getName(), notificationId ); + return; + } + + final String templateName = intent.getStringExtra( PublishOptions.TEMPLATE_NAME ); + if( templateName != null ) + { + AndroidPushTemplate androidPushTemplate = PushTemplateHelper.getPushNotificationTemplates().get( templateName ); + if( androidPushTemplate != null ) + { + if( androidPushTemplate.getContentAvailable() != null && androidPushTemplate.getContentAvailable() == 1 ) + return; + + Notification notification = PushTemplateHelper.convertFromTemplate( context, androidPushTemplate, intent.getExtras().deepCopy(), notificationId ); + intent.getExtras().deepCopy(); + PushTemplateHelper.showNotification( context, notification, androidPushTemplate.getName(), notificationId ); + } + return; + } + + + final String message = intent.getStringExtra( PublishOptions.MESSAGE_TAG ); + final String contentTitle = intent.getStringExtra( PublishOptions.ANDROID_CONTENT_TITLE_TAG ); + final String summarySubText = intent.getStringExtra( PublishOptions.ANDROID_SUMMARY_SUBTEXT_TAG ); + String soundResource = intent.getStringExtra( PublishOptions.ANDROID_CONTENT_SOUND_TAG ); + fallBackMode( context, message, contentTitle, summarySubText, soundResource, notificationId ); + } + catch ( Throwable throwable ) + { + Log.e( TAG, "Error processing push notification", throwable ); + } + } + + private void fallBackMode( Context context, String message, String contentTitle, String summarySubText, String soundResource, final int notificationId ) + { + final String channelName = "Fallback"; + final NotificationCompat.Builder notificationBuilder; + + // android.os.Build.VERSION_CODES.O == 26 + if( android.os.Build.VERSION.SDK_INT > 25 ) + { + final String channelId = Backendless.getApplicationId() + ":" + channelName; + NotificationManager notificationManager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE ); + NotificationChannel notificationChannel = notificationManager.getNotificationChannel( channelId ); + + if( notificationChannel == null ) + { + notificationChannel = new NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT ); + + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setUsage( AudioAttributes.USAGE_NOTIFICATION_RINGTONE ) + .setContentType( AudioAttributes.CONTENT_TYPE_SONIFICATION ) + .setFlags( AudioAttributes.FLAG_AUDIBILITY_ENFORCED ) + .setLegacyStreamType( AudioManager.STREAM_NOTIFICATION ) + .build(); + + notificationChannel.setSound( PushTemplateHelper.getSoundUri( context, soundResource ), audioAttributes ); + notificationManager.createNotificationChannel( notificationChannel ); + } + + notificationBuilder = new NotificationCompat.Builder( context, notificationChannel.getId() ); + } + else + notificationBuilder = new NotificationCompat.Builder( context ); + + notificationBuilder.setSound( PushTemplateHelper.getSoundUri( context, soundResource ), AudioManager.STREAM_NOTIFICATION ); + + int appIcon = context.getApplicationInfo().icon; + if( appIcon == 0 ) + appIcon = android.R.drawable.sym_def_app_icon; + + Intent notificationIntent = context.getPackageManager().getLaunchIntentForPackage( context.getApplicationInfo().packageName ); + PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationIntent, 0 ); + + notificationBuilder.setContentIntent( contentIntent ) + .setSmallIcon( appIcon ) + .setContentTitle( contentTitle ) + .setSubText( summarySubText ) + .setContentText( message ) + .setWhen( System.currentTimeMillis() ) + .setAutoCancel( true ) + .build(); + + final NotificationManagerCompat notificationManager = NotificationManagerCompat.from( context ); + Handler handler = new Handler( Looper.getMainLooper() ); + handler.post( new Runnable() + { + @Override + public void run() + { + notificationManager.notify( channelName, notificationId, notificationBuilder.build() ); + } + } ); + } + + private void refreshTokenOnBackendless( final Context context, String newDeviceToken ) + { + Backendless.Messaging.refreshDeviceToken( newDeviceToken, new AsyncCallback() + { + @Override + public void handleResponse( Boolean response ) + { + if( response ) + Log.d( TAG, "Device token refreshed successfully." ); + else + { + Log.d( TAG, "Device is not registered on any channel." ); + FCMRegistration.unregisterDeviceOnFCM( context, null ); + } + } + + @Override + public void handleFault( BackendlessFault fault ) + { + Log.e( TAG, "Can not refresh device token on Backendless. " + fault.getMessage() ); + } + } ); + } } diff --git a/src/com/backendless/push/BackendlessPushService.java b/src/com/backendless/push/BackendlessPushService.java index 9c2ceff25..05b6ff311 100644 --- a/src/com/backendless/push/BackendlessPushService.java +++ b/src/com/backendless/push/BackendlessPushService.java @@ -40,6 +40,8 @@ import java.util.concurrent.atomic.AtomicInteger; /** + * Deprecated. Use {@code BackendlessFCMService} instead. + * *

Firstly you should inherit this class for your own needs. For example to handle push messages manually. * *

Secondary you should declare this service in 'AndroidManifest.xml' like this:
@@ -51,20 +53,16 @@ * * Where {@code 'full.qualified.class.name'} is {@code 'com.backendless.push.BackendlessPushService'} or your own class that inherit it. */ +@Deprecated public class BackendlessPushService extends JobIntentService implements PushReceiverCallback { private static final String TAG = BackendlessPushService.class.getSimpleName(); - public static final String ACTION_FCM_REGISTRATION = "BackendlessPushService.fcm.registration"; - public static final String ACTION_FCM_UNREGISTRATION = "BackendlessPushService.fcm.unregistration"; - public static final String ACTION_FCM_REFRESH_TOKEN = "BackendlessPushService.fcm.refresh_token"; - public static final String ACTION_FCM_ONMESSAGE = "BackendlessPushService.fcm.onMessage"; - public static final String KEY_DEVICE_TOKEN = "BackendlessPushService.deviceToken"; public static final String KEY_CHANNELS = "BackendlessPushService.channels"; public static final String KEY_EXPIRATION = "BackendlessPushService.expiration"; - public static final String IMMEDIATE_MESSAGE = "ImmediateMessage"; + private static final String IMMEDIATE_MESSAGE = "ImmediateMessage"; private static final int JOB_ID = 1000; private static final Random random = new Random( System.currentTimeMillis() ); @@ -195,19 +193,6 @@ final protected void onHandleWork( @NonNull Intent intent ) case GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY: handleRetry( this, intent ); break; - - case BackendlessPushService.ACTION_FCM_REGISTRATION: - registerOnBackendless( this, intent ); - break; - case BackendlessPushService.ACTION_FCM_UNREGISTRATION: - unregisterOnBackendless( this, intent ); - break; - case BackendlessPushService.ACTION_FCM_REFRESH_TOKEN: - refreshTokenOnBackendless( this, intent ); - break; - case BackendlessPushService.ACTION_FCM_ONMESSAGE: - handleMessage( this, intent ); - break; } } @@ -448,7 +433,7 @@ private void registerFurther( final Context context, String GCMregistrationId ) @Override public void handleResponse( String registrationInfo ) { - Map channelRegistrations = processRegistrationPayload( context, registrationInfo ); + Map channelRegistrations = FCMRegistration.processRegistrationPayload( context, registrationInfo ); StringBuilder sb = new StringBuilder(); @@ -488,120 +473,6 @@ public void handleFault( BackendlessFault fault ) } ); } - private Map processRegistrationPayload( final Context context, final String payload ) - { - Object[] obj; - try - { - obj = (Object[]) weborb.util.io.Serializer.fromBytes( payload.getBytes(), weborb.util.io.Serializer.JSON, false ); - } - catch( IOException e ) - { - Log.e( TAG, "Could not deserialize server response: " + e.getMessage() ); - callback.onError( context, "Could not deserialize server response: " + e.getMessage() ); - return null; - } - - PushTemplateHelper.deleteNotificationChannel( context ); - Map templates = (Map) obj[ 1 ]; - - if( android.os.Build.VERSION.SDK_INT > 25 ) - { - for( AndroidPushTemplate templ : templates.values() ) - PushTemplateHelper.getOrCreateNotificationChannel( context.getApplicationContext(), templ ); - } - - PushTemplateHelper.setPushNotificationTemplates( templates, payload.getBytes() ); - - String regs = (String) obj[ 0 ]; - Map channelRegistrations = new HashMap<>(); - String[] regPairs = regs.split( "," ); - - for( String pair : regPairs ) - { - String[] valueKey = pair.split( "::" ); - channelRegistrations.put( valueKey[1], valueKey[0] ); - } - - return channelRegistrations; - } - - private void registerOnBackendless( final Context context, Intent intent ) - { - String deviceToken = intent.getStringExtra( BackendlessPushService.KEY_DEVICE_TOKEN ); - List channels = intent.getStringArrayListExtra( BackendlessPushService.KEY_CHANNELS ); - long expiration = intent.getLongExtra( BackendlessPushService.KEY_EXPIRATION, 0 ); - - Backendless.Messaging.registerDeviceOnServer( deviceToken, channels, expiration, new AsyncCallback() - { - @Override - public void handleResponse( String response ) - { - Log.d( TAG, "Registered on Backendless." ); - Map channelRegistrations = processRegistrationPayload( context, response ); - callback.onRegistered( context, channelRegistrations ); - } - - @Override - public void handleFault( BackendlessFault fault ) - { - Log.d( TAG, "Could not register device on Backendless server: " + fault.toString() ); - callback.onError( context, "Could not register device on Backendless server: " + fault.toString() ); - } - } ); - } - - private void unregisterOnBackendless( final Context context, Intent intent ) - { - List channels = intent.getStringArrayListExtra( BackendlessPushService.KEY_CHANNELS ); - - Backendless.Messaging.unregisterDeviceOnServer( channels, new AsyncCallback() - { - @Override - public void handleResponse( Integer response ) - { - Log.d( TAG, "Unregistered on Backendless." ); - if( response < 1 ) - FCMRegistration.unregisterDeviceOnFCM( context, callback ); - else - callback.onUnregistered( context, true ); - } - - @Override - public void handleFault( BackendlessFault fault ) - { - Log.d( TAG, "Could not unregister device on Backendless server: " + fault.toString() ); - callback.onError( context, "Could not unregister device on Backendless server: " + fault.toString() ); - } - } ); - } - - private void refreshTokenOnBackendless( final Context context, Intent intent ) - { - String newDeviceToken = intent.getStringExtra( BackendlessPushService.KEY_DEVICE_TOKEN ); - - Backendless.Messaging.refreshDeviceToken( newDeviceToken, new AsyncCallback() - { - @Override - public void handleResponse( Boolean response ) - { - if( response ) - Log.d( TAG, "Device token refreshed successfully." ); - else - { - Log.d( TAG, "Device is not registered on any channel." ); - FCMRegistration.unregisterDeviceOnFCM( context, callback ); - } - } - - @Override - public void handleFault( BackendlessFault fault ) - { - Log.e( TAG, "Can not refresh device token on Backendless. " + fault.getMessage() ); - } - } ); - } - public static boolean isFCM( Context appContext ) { if( isFCM != null ) diff --git a/src/com/backendless/push/FCMRegistration.java b/src/com/backendless/push/FCMRegistration.java index b405090d1..8805640ec 100644 --- a/src/com/backendless/push/FCMRegistration.java +++ b/src/com/backendless/push/FCMRegistration.java @@ -1,19 +1,23 @@ package com.backendless.push; import android.content.Context; -import android.content.Intent; import android.support.annotation.NonNull; import android.util.Log; +import com.backendless.Backendless; import com.backendless.async.callback.AsyncCallback; +import com.backendless.exceptions.BackendlessException; import com.backendless.exceptions.BackendlessFault; +import com.backendless.messaging.AndroidPushTemplate; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; -import java.util.ArrayList; +import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; public class FCMRegistration @@ -22,7 +26,7 @@ public class FCMRegistration private static final String TAG = FCMRegistration.class.getSimpleName(); public static void registerDevice( final Context appContext, final List channels, final long expiration, - final AsyncCallback callback ) + final AsyncCallback fcmCallback, final AsyncCallback> bkndlsCallback ) { FirebaseMessaging.getInstance().subscribeToTopic( DEFAULT_TOPIC ).addOnCompleteListener( new OnCompleteListener() { @@ -32,8 +36,8 @@ public void onComplete( @NonNull Task task ) if( !task.isSuccessful() ) { Log.e( TAG, "Failed to subscribe in FCM.", task.getException() ); - if (callback != null) - callback.handleFault( new BackendlessFault( "Failed to subscribe in FCM. " + task.getException().getMessage() ) ); + if (fcmCallback != null) + fcmCallback.handleFault( new BackendlessFault( "Failed to subscribe in FCM. " + task.getException().getMessage() ) ); } else FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener( new OnCompleteListener() @@ -44,20 +48,17 @@ public void onComplete( @NonNull Task task ) if( !task.isSuccessful() ) { Log.e( TAG, "Can not retrieve deviceToken from FCM.", task.getException() ); - if( callback != null ) - callback.handleFault( new BackendlessFault( "Can not retrieve deviceToken from FCM. " + task.getException().getMessage() ) ); + if( fcmCallback != null ) + fcmCallback.handleFault( new BackendlessFault( "Can not retrieve deviceToken from FCM. " + task.getException().getMessage() ) ); } else { String deviceToken = task.getResult().getToken(); - Intent msgWork = new Intent( BackendlessPushService.ACTION_FCM_REGISTRATION ); - msgWork.putExtra( BackendlessPushService.KEY_DEVICE_TOKEN, deviceToken ); - msgWork.putStringArrayListExtra( BackendlessPushService.KEY_CHANNELS, new ArrayList<>( channels ) ); - msgWork.putExtra( BackendlessPushService.KEY_EXPIRATION, expiration ); - BackendlessPushService.enqueueWork( appContext, msgWork ); - - if( callback != null ) - callback.handleResponse( deviceToken ); + + if( fcmCallback != null ) + fcmCallback.handleResponse( deviceToken ); + + FCMRegistration.registerOnBackendless( appContext, deviceToken, channels, expiration, bkndlsCallback ); } } } ); @@ -65,14 +66,60 @@ public void onComplete( @NonNull Task task ) } ); } - public static void unregisterDevice( final Context appContext, final List channels ) + private static void registerOnBackendless( final Context appContext, String deviceToken, List channels, long expiration, final AsyncCallback> bkndlsCallback ) { - Intent msgWork = new Intent( BackendlessPushService.ACTION_FCM_UNREGISTRATION ); - msgWork.putStringArrayListExtra( BackendlessPushService.KEY_CHANNELS, new ArrayList<>(channels) ); - BackendlessPushService.enqueueWork( appContext, msgWork ); + Backendless.Messaging.registerDeviceOnServer( deviceToken, channels, expiration, new AsyncCallback() + { + @Override + public void handleResponse( String registrationInfo ) + { + Log.d( TAG, "Registered on Backendless." ); + try + { + Map channelRegistrations = processRegistrationPayload( appContext, registrationInfo ); + if( bkndlsCallback != null ) + bkndlsCallback.handleResponse( channelRegistrations ); + } + catch( Exception e ) + { + bkndlsCallback.handleFault( new BackendlessFault( "Could not deserialize server response: " + e.getMessage() ) ); + } + } + + @Override + public void handleFault( BackendlessFault fault ) + { + Log.d( TAG, "Could not register device on Backendless server: " + fault.toString() ); + if( bkndlsCallback != null ) + bkndlsCallback.handleFault( new BackendlessFault( "Could not register device on Backendless server: " + fault.toString() ) ); + } + } ); } - public static void unregisterDeviceOnFCM(final Context context, final PushReceiverCallback callback) + public static void unregisterDevice( final Context appContext, final List channels, final AsyncCallback callback ) + { + Backendless.Messaging.unregisterDeviceOnServer( channels, new AsyncCallback() + { + @Override + public void handleResponse( Integer response ) + { + Log.d( TAG, "Unregistered on Backendless." ); + if( response < 1 ) + FCMRegistration.unregisterDeviceOnFCM( appContext, callback ); + else + callback.handleResponse( response ); + } + + @Override + public void handleFault( BackendlessFault fault ) + { + Log.d( TAG, "Could not unregister device on Backendless server: " + fault.toString() ); + callback.handleFault( new BackendlessFault( "Could not unregister device on Backendless server: " + fault.toString() ) ); + } + } ); + } + + static void unregisterDeviceOnFCM(final Context context, final AsyncCallback callback) { FirebaseMessaging.getInstance().unsubscribeFromTopic( DEFAULT_TOPIC ).addOnCompleteListener( new OnCompleteListener() { @@ -82,15 +129,52 @@ public void onComplete( @NonNull Task task ) if( task.isSuccessful() ) { Log.d( TAG, "Unsubscribed on FCM." ); - callback.onUnregistered( context, true ); + callback.handleResponse( 0 ); } else { Log.e( TAG, "Failed to unsubscribe in FCM.", task.getException() ); String reason = (task.getException() != null) ? Objects.toString( task.getException().getMessage() ) : ""; - callback.onError( context, "Failed to unsubscribe on FCM. " + reason ); + callback.handleFault( new BackendlessFault( "Failed to unsubscribe on FCM. " + reason ) ); } } } ); } + + static Map processRegistrationPayload( final Context context, final String registrationInfo ) + { + Object[] obj; + try + { + obj = (Object[]) weborb.util.io.Serializer.fromBytes( registrationInfo.getBytes(), weborb.util.io.Serializer.JSON, false ); + } + catch( IOException e ) + { + Log.e( TAG, "Could not deserialize server response: " + e.getMessage() ); + throw new BackendlessException( "Could not deserialize server response: " + e.getMessage() ); + } + + PushTemplateHelper.deleteNotificationChannel( context ); + Map templates = (Map) obj[ 1 ]; + + if( android.os.Build.VERSION.SDK_INT > 25 ) + { + for( AndroidPushTemplate templ : templates.values() ) + PushTemplateHelper.getOrCreateNotificationChannel( context.getApplicationContext(), templ ); + } + + PushTemplateHelper.setPushNotificationTemplates( templates, registrationInfo.getBytes() ); + + String regs = (String) obj[ 0 ]; + Map channelRegistrations = new HashMap<>(); + String[] regPairs = regs.split( "," ); + + for( String pair : regPairs ) + { + String[] valueKey = pair.split( "::" ); + channelRegistrations.put( valueKey[1], valueKey[0] ); + } + + return channelRegistrations; + } } From f456377ae08809cf0388724445e2223948f59848 Mon Sep 17 00:00:00 2001 From: oleg Date: Mon, 12 Nov 2018 15:40:08 +0200 Subject: [PATCH 2/6] Added DeviceRegistrationResult. --- src/com/backendless/Messaging.java | 14 +++--- .../push/DeviceRegistrationResult.java | 43 +++++++++++++++++++ src/com/backendless/push/FCMRegistration.java | 36 +++++++++------- 3 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 src/com/backendless/push/DeviceRegistrationResult.java diff --git a/src/com/backendless/Messaging.java b/src/com/backendless/Messaging.java index b143d0239..77637ad9c 100644 --- a/src/com/backendless/Messaging.java +++ b/src/com/backendless/Messaging.java @@ -50,6 +50,7 @@ import com.backendless.messaging.PublishStatusEnum; import com.backendless.messaging.PushBroadcastMask; import com.backendless.push.BackendlessPushService; +import com.backendless.push.DeviceRegistrationResult; import com.backendless.push.FCMRegistration; import com.backendless.push.GCMRegistrar; import com.backendless.rt.messaging.Channel; @@ -62,7 +63,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.UUID; public final class Messaging @@ -274,23 +274,21 @@ public void registerDevice( List channels ) */ public void registerDevice( List channels, Date expiration ) { - registerDevice( channels, expiration, (AsyncCallback) null, (AsyncCallback>)null ); + registerDevice( channels, expiration, (AsyncCallback) null ); } /** * For FireBase messaging only. */ - public void registerDevice( List channels, AsyncCallback fcmCallback, AsyncCallback> bkndlsCallback ) + public void registerDevice( List channels, AsyncCallback callback ) { - registerDevice( channels, (Date) null, fcmCallback, bkndlsCallback ); + registerDevice( channels, (Date) null, callback ); } /** * For FireBase messaging only. - * @param fcmCallback Triggered on successful/error event during registration on Google FCM. On success receives deviceToken. - * @param bkndlsCallback Triggered on successful/error event during registration on Backendless server. On success receive channelRegistrations map, where key is a channel name and value is a device registration id (table DeviceRegistrations). */ - public void registerDevice( List channels, Date expiration, AsyncCallback fcmCallback, AsyncCallback> bkndlsCallback ) + public void registerDevice( List channels, Date expiration, AsyncCallback callback ) { if( !BackendlessPushService.isFCM( ContextHandler.getAppContext() ) ) throw new IllegalStateException( "The method is intended only for FireBase messaging." ); @@ -312,7 +310,7 @@ public void registerDevice( List channels, Date expiration, AsyncCallbac else expirationMs = expiration.getTime(); } - FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, fcmCallback, bkndlsCallback ); + FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, callback ); } private void checkChannelName( String channelName ) diff --git a/src/com/backendless/push/DeviceRegistrationResult.java b/src/com/backendless/push/DeviceRegistrationResult.java new file mode 100644 index 000000000..19eb2b610 --- /dev/null +++ b/src/com/backendless/push/DeviceRegistrationResult.java @@ -0,0 +1,43 @@ +package com.backendless.push; + +import java.util.Map; + + +public class DeviceRegistrationResult +{ + private String deviceToken; + private Map channelRegistrations; + + /** + * The device token that device receives after registration on Google FCM. + * + * @return + */ + public String getDeviceToken() + { + return deviceToken; + } + + /** + *

The map, where key is a channel name and value is a device registration id (table DeviceRegistrations). + *

It is received after successful registration on Backendless server. + * + * @return + */ + public Map getChannelRegistrations() + { + return channelRegistrations; + } + + DeviceRegistrationResult setDeviceToken( String deviceToken ) + { + this.deviceToken = deviceToken; + return this; + } + + DeviceRegistrationResult setChannelRegistrations( Map channelRegistrations ) + { + this.channelRegistrations = channelRegistrations; + return this; + } +} diff --git a/src/com/backendless/push/FCMRegistration.java b/src/com/backendless/push/FCMRegistration.java index 8805640ec..923aa67b0 100644 --- a/src/com/backendless/push/FCMRegistration.java +++ b/src/com/backendless/push/FCMRegistration.java @@ -26,7 +26,7 @@ public class FCMRegistration private static final String TAG = FCMRegistration.class.getSimpleName(); public static void registerDevice( final Context appContext, final List channels, final long expiration, - final AsyncCallback fcmCallback, final AsyncCallback> bkndlsCallback ) + final AsyncCallback callback ) { FirebaseMessaging.getInstance().subscribeToTopic( DEFAULT_TOPIC ).addOnCompleteListener( new OnCompleteListener() { @@ -36,8 +36,8 @@ public void onComplete( @NonNull Task task ) if( !task.isSuccessful() ) { Log.e( TAG, "Failed to subscribe in FCM.", task.getException() ); - if (fcmCallback != null) - fcmCallback.handleFault( new BackendlessFault( "Failed to subscribe in FCM. " + task.getException().getMessage() ) ); + if (callback != null) + callback.handleFault( new BackendlessFault( "Failed to subscribe in FCM. " + task.getException().getMessage() ) ); } else FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener( new OnCompleteListener() @@ -48,17 +48,21 @@ public void onComplete( @NonNull Task task ) if( !task.isSuccessful() ) { Log.e( TAG, "Can not retrieve deviceToken from FCM.", task.getException() ); - if( fcmCallback != null ) - fcmCallback.handleFault( new BackendlessFault( "Can not retrieve deviceToken from FCM. " + task.getException().getMessage() ) ); + if( callback != null ) + callback.handleFault( new BackendlessFault( "Can not retrieve deviceToken from FCM. " + task.getException().getMessage() ) ); } else { String deviceToken = task.getResult().getToken(); - if( fcmCallback != null ) - fcmCallback.handleResponse( deviceToken ); + DeviceRegistrationResult devRegResult = null; + if( callback != null ) + { + devRegResult = new DeviceRegistrationResult(); + callback.handleResponse( devRegResult.setDeviceToken( deviceToken ) ); + } - FCMRegistration.registerOnBackendless( appContext, deviceToken, channels, expiration, bkndlsCallback ); + FCMRegistration.registerOnBackendless( appContext, deviceToken, channels, expiration, callback, devRegResult ); } } } ); @@ -66,7 +70,7 @@ public void onComplete( @NonNull Task task ) } ); } - private static void registerOnBackendless( final Context appContext, String deviceToken, List channels, long expiration, final AsyncCallback> bkndlsCallback ) + private static void registerOnBackendless( final Context appContext, String deviceToken, List channels, long expiration, final AsyncCallback callback, final DeviceRegistrationResult devRegResult ) { Backendless.Messaging.registerDeviceOnServer( deviceToken, channels, expiration, new AsyncCallback() { @@ -76,13 +80,15 @@ public void handleResponse( String registrationInfo ) Log.d( TAG, "Registered on Backendless." ); try { - Map channelRegistrations = processRegistrationPayload( appContext, registrationInfo ); - if( bkndlsCallback != null ) - bkndlsCallback.handleResponse( channelRegistrations ); + if( callback != null ) + { + Map channelRegistrations = processRegistrationPayload( appContext, registrationInfo ); + callback.handleResponse( devRegResult.setChannelRegistrations( channelRegistrations ) ); + } } catch( Exception e ) { - bkndlsCallback.handleFault( new BackendlessFault( "Could not deserialize server response: " + e.getMessage() ) ); + callback.handleFault( new BackendlessFault( "Could not deserialize server response: " + e.getMessage() ) ); } } @@ -90,8 +96,8 @@ public void handleResponse( String registrationInfo ) public void handleFault( BackendlessFault fault ) { Log.d( TAG, "Could not register device on Backendless server: " + fault.toString() ); - if( bkndlsCallback != null ) - bkndlsCallback.handleFault( new BackendlessFault( "Could not register device on Backendless server: " + fault.toString() ) ); + if( callback != null ) + callback.handleFault( new BackendlessFault( "Could not register device on Backendless server: " + fault.toString() ) ); } } ); } From 79df96fbf0974896572b4ac7072dbd4116c13515 Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 15 Nov 2018 16:02:36 +0200 Subject: [PATCH 3/6] BKNDLSS-17770 Separate GCM and FCM logic. --- src/com/backendless/push/FCMRegistration.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/com/backendless/push/FCMRegistration.java b/src/com/backendless/push/FCMRegistration.java index 923aa67b0..0b1303b6e 100644 --- a/src/com/backendless/push/FCMRegistration.java +++ b/src/com/backendless/push/FCMRegistration.java @@ -54,14 +54,7 @@ public void onComplete( @NonNull Task task ) else { String deviceToken = task.getResult().getToken(); - - DeviceRegistrationResult devRegResult = null; - if( callback != null ) - { - devRegResult = new DeviceRegistrationResult(); - callback.handleResponse( devRegResult.setDeviceToken( deviceToken ) ); - } - + DeviceRegistrationResult devRegResult = (callback != null) ? new DeviceRegistrationResult().setDeviceToken( deviceToken ) : null; FCMRegistration.registerOnBackendless( appContext, deviceToken, channels, expiration, callback, devRegResult ); } } From 7a0d4ebad0e86038c5b290625ca0428fea494ee5 Mon Sep 17 00:00:00 2001 From: oleg Date: Mon, 19 Nov 2018 12:41:41 +0200 Subject: [PATCH 4/6] Added toString method for DeviceRegistrationResult. --- src/com/backendless/push/DeviceRegistrationResult.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/backendless/push/DeviceRegistrationResult.java b/src/com/backendless/push/DeviceRegistrationResult.java index 19eb2b610..0cd78cc7c 100644 --- a/src/com/backendless/push/DeviceRegistrationResult.java +++ b/src/com/backendless/push/DeviceRegistrationResult.java @@ -40,4 +40,10 @@ DeviceRegistrationResult setChannelRegistrations( Map channelReg this.channelRegistrations = channelRegistrations; return this; } + + @Override + public String toString() + { + return "DeviceRegistrationResult{" + "deviceToken='" + deviceToken + '\'' + ", channelRegistrations=" + channelRegistrations + '}'; + } } From b88f710bea8cb6c11ea940fb7f744f454e3daabb Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 20 Nov 2018 16:34:49 +0200 Subject: [PATCH 5/6] BKNDLSS-17829 App crashes with NPE when unregistering device with methods without a callback argument --- src/com/backendless/push/FCMRegistration.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/com/backendless/push/FCMRegistration.java b/src/com/backendless/push/FCMRegistration.java index 0b1303b6e..54f9456e5 100644 --- a/src/com/backendless/push/FCMRegistration.java +++ b/src/com/backendless/push/FCMRegistration.java @@ -105,7 +105,7 @@ public void handleResponse( Integer response ) Log.d( TAG, "Unregistered on Backendless." ); if( response < 1 ) FCMRegistration.unregisterDeviceOnFCM( appContext, callback ); - else + else if( callback != null ) callback.handleResponse( response ); } @@ -113,7 +113,8 @@ public void handleResponse( Integer response ) public void handleFault( BackendlessFault fault ) { Log.d( TAG, "Could not unregister device on Backendless server: " + fault.toString() ); - callback.handleFault( new BackendlessFault( "Could not unregister device on Backendless server: " + fault.toString() ) ); + if (callback != null) + callback.handleFault( new BackendlessFault( "Could not unregister device on Backendless server: " + fault.toString() ) ); } } ); } @@ -128,13 +129,15 @@ public void onComplete( @NonNull Task task ) if( task.isSuccessful() ) { Log.d( TAG, "Unsubscribed on FCM." ); - callback.handleResponse( 0 ); + if( callback != null ) + callback.handleResponse( 0 ); } else { Log.e( TAG, "Failed to unsubscribe in FCM.", task.getException() ); String reason = (task.getException() != null) ? Objects.toString( task.getException().getMessage() ) : ""; - callback.handleFault( new BackendlessFault( "Failed to unsubscribe on FCM. " + reason ) ); + if( callback != null ) + callback.handleFault( new BackendlessFault( "Failed to unsubscribe on FCM. " + reason ) ); } } } ); From 32e918b1ccbd2f1300c4542d38ec39d8df437e10 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 26 Nov 2018 21:07:27 -0600 Subject: [PATCH 6/6] fixed error messages --- src/com/backendless/Messaging.java | 22 +++++++++++++------ .../push/BackendlessPushService.java | 13 ++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/com/backendless/Messaging.java b/src/com/backendless/Messaging.java index 77637ad9c..fe2246dc9 100644 --- a/src/com/backendless/Messaging.java +++ b/src/com/backendless/Messaging.java @@ -254,7 +254,7 @@ private synchronized void registerDeviceGCMSync( Context context, String GCMSend } /** - * For FireBase messaging only. + * For Firebase Cloud Messaging only */ public void registerDevice() { @@ -262,7 +262,7 @@ public void registerDevice() } /** - * For FireBase messaging only. + * For Firebase Cloud Messaging only */ public void registerDevice( List channels ) { @@ -270,7 +270,7 @@ public void registerDevice( List channels ) } /** - * For FireBase messaging only. + * For Firebase Cloud Messaging only */ public void registerDevice( List channels, Date expiration ) { @@ -278,7 +278,15 @@ public void registerDevice( List channels, Date expiration ) } /** - * For FireBase messaging only. + * For Firebase Cloud Messaging only + */ + public void registerDevice( AsyncCallback callback ) + { + registerDevice( Collections.singletonList( DEFAULT_CHANNEL_NAME ), (Date) null, callback ); + } + + /** + * For Firebase Cloud Messaging only */ public void registerDevice( List channels, AsyncCallback callback ) { @@ -286,7 +294,7 @@ public void registerDevice( List channels, AsyncCallback channels, Date expiration, AsyncCallback callback ) { @@ -295,14 +303,13 @@ public void registerDevice( List channels, Date expiration, AsyncCallbac if( channels == null || channels.isEmpty() || (channels.size() == 1 && (channels.get( 0 ) == null || channels.get( 0 ).isEmpty())) ) - { channels = Collections.singletonList( DEFAULT_CHANNEL_NAME ); - } for( String channel : channels ) checkChannelName( channel ); long expirationMs = 0; + if( expiration != null) { if( expiration.before( Calendar.getInstance().getTime() ) ) @@ -310,6 +317,7 @@ public void registerDevice( List channels, Date expiration, AsyncCallbac else expirationMs = expiration.getTime(); } + FCMRegistration.registerDevice( ContextHandler.getAppContext(), channels, expirationMs, callback ); } diff --git a/src/com/backendless/push/BackendlessPushService.java b/src/com/backendless/push/BackendlessPushService.java index 05b6ff311..20c81a3d2 100644 --- a/src/com/backendless/push/BackendlessPushService.java +++ b/src/com/backendless/push/BackendlessPushService.java @@ -486,7 +486,7 @@ public static boolean isFCM( Context appContext ) } catch( ClassNotFoundException e ) { - Log.i( TAG, "Class FirebaseMessagingService not found. GCM will be used." ); + Log.e( TAG, "Class FirebaseMessagingService cannot be found. FCM is not properly configured in your application." ); return isFCM = false; } @@ -507,8 +507,9 @@ public static boolean isFCM( Context appContext ) } catch( ClassNotFoundException e ) { - Log.e( TAG, "Can not load declared service class.", e ); - throw new IllegalStateException( "Can not load declared service class.", e ); + String error = "Unable to load com.backendless.push.BackendlessFCMService"; + Log.e( TAG, error, e ); + throw new IllegalStateException( error, e ); } for( ServiceInfo srvInfo : services ) @@ -524,13 +525,13 @@ public static boolean isFCM( Context appContext ) } catch( ClassNotFoundException e ) { - Log.w( TAG, "You have declared class in AndroidManifest.xml that is not present in your app.", e ); + Log.e( TAG, "An FCM service class is registered in AndroidManifest.xml but it cannot be found in your app. The class name is " + srvInfo.name, e ); } } if( !flag ) { - Log.i( TAG, "Class FirebaseMessagingService not found. GCM will be used." ); + Log.i( TAG, "Class FirebaseMessagingService cannot found. FCM is not properly configured in your app." ); return isFCM = false; } } @@ -546,7 +547,7 @@ public static boolean isFCM( Context appContext ) List srvIntentFilters = packageManager.queryIntentServices( intent, PackageManager.GET_INTENT_FILTERS ); if( srvIntentFilters.isEmpty() ) - throw new IllegalStateException( "Missed intent-filter action " + action ); + throw new IllegalStateException( "Missing the intent-filter action: " + action ); return isFCM = true; }