package com.backendless.push; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.support.annotation.NonNull; import android.util.Log; 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.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; public class FCMRegistration { private static final String DEFAULT_TOPIC = "default-topic"; private static final String TAG = FCMRegistration.class.getSimpleName(); private static boolean fcmConfig = false; public static void registerDevice( final Context appContext, final List<String> channels, final long expiration, final AsyncCallback<DeviceRegistrationResult> callback ) { FCMRegistration.checkConfiguration( appContext ); FirebaseMessaging.getInstance().subscribeToTopic( DEFAULT_TOPIC ).addOnCompleteListener( new OnCompleteListener<Void>() { @Override public void onComplete( @NonNull Task<Void> 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() ) ); } else FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener( new OnCompleteListener<InstanceIdResult>() { @Override public void onComplete( @NonNull Task<InstanceIdResult> 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() ) ); } else { String deviceToken = task.getResult().getToken(); DeviceRegistrationResult devRegResult = (callback != null) ? new DeviceRegistrationResult().setDeviceToken( deviceToken ) : null; FCMRegistration.registerOnBackendless( appContext, deviceToken, channels, expiration, callback, devRegResult ); } } } ); } } ); } private static void registerOnBackendless( final Context appContext, String deviceToken, List<String> channels, long expiration, final AsyncCallback<DeviceRegistrationResult> callback, final DeviceRegistrationResult devRegResult ) { DeviceRegistrationUtil.getInstance().registerDeviceOnServer( deviceToken, channels, expiration, new AsyncCallback<String>() { @Override public void handleResponse( String registrationInfo ) { Log.d( TAG, "Registered on Backendless." ); try { if( callback != null ) { Map<String, String> channelRegistrations = processRegistrationPayload( appContext, registrationInfo ); callback.handleResponse( devRegResult.setChannelRegistrations( channelRegistrations ) ); } } catch( Exception e ) { callback.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( callback != null ) callback.handleFault( new BackendlessFault( "Could not register device on Backendless server: " + fault.toString() ) ); } } ); } public static void unregisterDevice( final Context appContext, final List<String> channels, final AsyncCallback<Integer> callback ) { FCMRegistration.checkConfiguration( appContext ); DeviceRegistrationUtil.getInstance().unregisterDeviceOnServer( channels, new AsyncCallback<Integer>() { @Override public void handleResponse( Integer response ) { Log.d( TAG, "Unregistered on Backendless." ); if( response < 1 ) FCMRegistration.unregisterDeviceOnFCM( appContext, callback ); else if( callback != null ) callback.handleResponse( response ); } @Override public void handleFault( BackendlessFault fault ) { Log.d( TAG, "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() ) ); } } ); } static void unregisterDeviceOnFCM(final Context context, final AsyncCallback<Integer> callback) { FirebaseMessaging.getInstance().unsubscribeFromTopic( DEFAULT_TOPIC ).addOnCompleteListener( new OnCompleteListener<Void>() { @Override public void onComplete( @NonNull Task<Void> task ) { if( task.isSuccessful() ) { Log.d( TAG, "Unsubscribed on FCM." ); 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() ) : ""; if( callback != null ) callback.handleFault( new BackendlessFault( "Failed to unsubscribe on FCM. " + reason ) ); } } } ); } private static Map<String, String> 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<String, AndroidPushTemplate> templates = (Map<String, AndroidPushTemplate>) 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<String, String> channelRegistrations = new HashMap<>(); String[] regPairs = regs.split( "," ); for( String pair : regPairs ) { String[] valueKey = pair.split( "::" ); channelRegistrations.put( valueKey[1], valueKey[0] ); } return channelRegistrations; } private static void checkConfiguration( Context appContext ) { if( fcmConfig ) return; ClassLoader clsLoader = appContext.getClassLoader(); try { clsLoader.loadClass( "com.google.firebase.messaging.FirebaseMessagingService" ); } catch( ClassNotFoundException e ) { String errorMsg = "Class FirebaseMessagingService cannot be found. FCM is not properly configured in your application."; Log.e( TAG, errorMsg ); throw new IllegalStateException( errorMsg, e ); } PackageManager packageManager = appContext.getPackageManager(); PackageInfo packageInfo; String srvClassName = null; try { packageInfo = packageManager.getPackageInfo( appContext.getPackageName(), PackageManager.GET_SERVICES ); ServiceInfo[] services = packageInfo.services; boolean flag = false; Class<?> fcmService; try { fcmService = clsLoader.loadClass( "com.backendless.push.BackendlessFCMService" ); } catch( ClassNotFoundException e ) { String errorMsg = "Unable to load com.backendless.push.BackendlessFCMService"; Log.e( TAG, errorMsg, e ); throw new IllegalStateException( errorMsg, e ); } for( ServiceInfo srvInfo : services ) { try { if( fcmService.isAssignableFrom( clsLoader.loadClass( srvInfo.name ) ) ) { flag = true; srvClassName = srvInfo.name; break; } } catch( ClassNotFoundException 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 ) { String errorMsg = "Make sure com.backendless.push.BackendlessFCMService or it's inheritor is registered in the Android manifest."; Log.i( TAG, errorMsg ); throw new IllegalStateException( errorMsg ); } } catch( PackageManager.NameNotFoundException e ) { String errorMsg = "Can not load current app package."; Log.e( TAG, errorMsg ); throw new IllegalStateException( errorMsg, e ); } final String action = "com.google.firebase.MESSAGING_EVENT"; Intent intent = new Intent( action ); intent.setClassName( appContext.getPackageName(), srvClassName ); List<ResolveInfo> srvIntentFilters = packageManager.queryIntentServices( intent, PackageManager.GET_INTENT_FILTERS ); if( srvIntentFilters.isEmpty() ) { String errorMsg = "Missing the intent-filter action: " + action; Log.e( TAG, errorMsg ); throw new IllegalStateException( errorMsg ); } fcmConfig = true; } }