Skip to content

Instantly share code, notes, and snippets.

@Mehuge
Created November 2, 2016 18:01
Show Gist options
  • Save Mehuge/374ee24d9e18a6c7ccc171d3e521b7ad to your computer and use it in GitHub Desktop.
Save Mehuge/374ee24d9e18a6c7ccc171d3e521b7ad to your computer and use it in GitHub Desktop.
Firebase integration into Cordova 6 Android Client
package com.redskyit.mobile.rmcv2.firebase;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import android.os.Bundle;
import android.content.SharedPreferences;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.messaging.FirebaseMessaging;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import me.leolin.shortcutbadger.ShortcutBadger;
public class FirebasePlugin extends CordovaPlugin {
private FirebaseAnalytics mFirebaseAnalytics;
private static String TAG = "RMCv2:FBPlugin";
protected static final String KEY = "badge";
protected static Bundle notificationBundle;
private static CallbackContext callbackNotificationOpen;
private static CallbackContext callbackTokenChanged;
@Override
protected void pluginInitialize() {
final Context context = this.cordova.getActivity().getApplicationContext();
Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
if (bundle != null && (bundle.containsKey("google.message_id") || bundle.containsKey("google.sent_time"))) {
// if initialising due to an notification being opened, store the bundle (data)
notificationBundle = bundle;
}
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
Log.d(TAG, "Starting Firebase plugin");
mFirebaseAnalytics = FirebaseAnalytics.getInstance(context);
// TODO: don't really need this?
FirebaseMessaging.getInstance().subscribeToTopic("android");
FirebaseMessaging.getInstance().subscribeToTopic("all");
Log.d(TAG, "subscribed to topics android, all");
}
});
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("getInstanceId")) {
this.getInstanceId(callbackContext);
return true;
} else if (action.equals("setBadgeNumber")) {
this.setBadgeNumber(callbackContext, args.getInt(0));
return true;
} else if (action.equals("getBadgeNumber")) {
this.getBadgeNumber(callbackContext);
return true;
} else if (action.equals("subscribe")) {
this.subscribe(callbackContext, args.getString(0));
return true;
} else if (action.equals("unsubscribe")) {
this.unsubscribe(callbackContext, args.getString(0));
return true;
} else if (action.equals("onNotificationOpen")) {
this.registerOnNotificationOpen(callbackContext);
return true;
} else if (action.equals("onTokenChanged")) {
this.registerOnTokenChanged(callbackContext);
return true;
} else if (action.equals("logEvent")) {
this.logEvent(callbackContext, args.getString(0), args.getJSONObject(1));
return true;
} else if (action.equals("setUserId")) {
this.setUserId(callbackContext, args.getString(0));
return true;
} else if (action.equals("setUserProperty")) {
this.setUserProperty(callbackContext, args.getString(0), args.getString(1));
return true;
} else if (action.equals("activateFetched")) {
this.activateFetched(callbackContext);
return true;
} else if (action.equals("fetch")) {
if (args.length() > 0) this.fetch(callbackContext, args.getLong(0));
else this.fetch(callbackContext);
return true;
} else if (action.equals("getByteArray")) {
if (args.length() > 1)
this.getByteArray(callbackContext, args.getString(0), args.getString(1));
else this.getByteArray(callbackContext, args.getString(0), null);
return true;
} else if (action.equals("getValue")) {
if (args.length() > 1)
this.getValue(callbackContext, args.getString(0), args.getString(1));
else this.getValue(callbackContext, args.getString(0), null);
return true;
} else if (action.equals("getInfo")) {
this.getInfo(callbackContext);
return true;
} else if (action.equals("setConfigSettings")) {
this.setConfigSettings(callbackContext, args.getJSONObject(0));
return true;
} else if (action.equals("setDefaults")) {
if (args.length() > 1)
this.setDefaults(callbackContext, args.getJSONObject(0), args.getString(1));
else this.setDefaults(callbackContext, args.getJSONObject(0), null);
return true;
}
return false;
}
// called when in foreground, in response to a notifcation broadcast
public static void onBroadcastReceive(Context context, Intent intent) {
Log.d("FirebasePlugin", "onBroadcastReceive (never called?)");
Bundle data = intent.getExtras();
data.putString("broadcast", "true");
FirebasePlugin.handleNotificationBundle(data);
}
private static boolean paused;
public static void setPaused(boolean paused) {
FirebasePlugin.paused = paused;
}
public static boolean isPaused() {
return FirebasePlugin.paused;
}
// called when in background
@Override
public void onNewIntent(Intent intent) {
Log.d(TAG, "new intent " + intent);
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
boolean isPush = data.getBoolean("rmc.is_push");
String id = data.getString("id");
Log.d(TAG, "INTENT DATA: is_push " + isPush + " id ");
if (null != id) {
data.putString("opened", "true");
FirebasePlugin.handleNotificationBundle(data);
} else {
Log.d(TAG, "Not a notification intent, ignored");
}
}
}
private void registerOnNotificationOpen(final CallbackContext callbackContext) {
FirebasePlugin.callbackNotificationOpen = callbackContext;
}
private void registerOnTokenChanged(final CallbackContext callbackContext) {
FirebasePlugin.callbackTokenChanged = callbackContext;
}
private static JSONObject bundle2json(Bundle bundle) throws JSONException {
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object obj = bundle.get(key);
if (obj instanceof Bundle) {
obj = bundle2json((Bundle)obj);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
json.put(key, JSONObject.wrap(obj));
} else {
json.put(key, obj);
}
}
return json;
}
public static void handleNotificationBundle(Bundle bundle) {
if (FirebasePlugin.callbackNotificationOpen == null ) {
Log.d(TAG, "no callback context, onNotificationOpen ignored");
return;
}
if (bundle != null) {
JSONObject json;
try {
json = bundle2json(bundle);
} catch(JSONException e) {
Log.d(TAG, "onNotificationOpen: json exception");
PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
result.setKeepCallback(true);
callbackNotificationOpen.sendPluginResult(result);
return;
}
Log.d(TAG, "onNotificationOpen: *** send notification to javascript ***");
PluginResult result = new PluginResult(PluginResult.Status.OK, json);
result.setKeepCallback(true);
callbackNotificationOpen.sendPluginResult(result);
Log.d(TAG, "onNotificationOpen: *** DONE send notification to javascript ***");
}
}
public static void onTokenChanged(String token) {
if (FirebasePlugin.callbackTokenChanged == null ) {
Log.d(TAG, "no callback context, onTokenChanged ignored");
return;
}
Log.d(TAG, "onNotificationOpen: send token to javascript");
PluginResult result = new PluginResult(PluginResult.Status.OK, token);
result.setKeepCallback(true);
callbackTokenChanged.sendPluginResult(result);
}
private void getInstanceId(final CallbackContext callbackContext) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "FirebaseInstanceId.getInstanceId() = " + token);
callbackContext.success(token);
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void setBadgeNumber(final CallbackContext callbackContext, final int number) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
Context context = cordova.getActivity();
SharedPreferences.Editor editor = context.getSharedPreferences(KEY, Context.MODE_PRIVATE).edit();
editor.putInt(KEY, number);
editor.apply();
ShortcutBadger.applyCount(context, number);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void getBadgeNumber(final CallbackContext callbackContext) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
Context context = cordova.getActivity();
SharedPreferences settings = context.getSharedPreferences(KEY, Context.MODE_PRIVATE);
int number = settings.getInt(KEY, 0);
callbackContext.success(number);
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void subscribe(final CallbackContext callbackContext, final String topic) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
FirebaseMessaging.getInstance().subscribeToTopic(topic);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void unsubscribe(final CallbackContext callbackContext, final String topic) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void logEvent(final CallbackContext callbackContext, final String name, final JSONObject params) throws JSONException {
final Bundle bundle = new Bundle();
Iterator iter = params.keys();
while (iter.hasNext()) {
String key = (String) iter.next();
Object value = params.get(key);
if (value instanceof Integer || value instanceof Double) {
bundle.putFloat(key, ((Number) value).floatValue());
} else {
bundle.putString(key, value.toString());
}
}
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
Log.d(TAG, "LOG EVENT: " + name + ": " + bundle);
mFirebaseAnalytics.logEvent(name, bundle);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void setUserId(final CallbackContext callbackContext, final String id) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
mFirebaseAnalytics.setUserId(id);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void setUserProperty(final CallbackContext callbackContext, final String name, final String value) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
mFirebaseAnalytics.setUserProperty(name, value);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void activateFetched(final CallbackContext callbackContext) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
final boolean activated = FirebaseRemoteConfig.getInstance().activateFetched();
callbackContext.success(String.valueOf(activated));
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void fetch(CallbackContext callbackContext) {
fetch(callbackContext, FirebaseRemoteConfig.getInstance().fetch());
}
private void fetch(CallbackContext callbackContext, long cacheExpirationSeconds) {
fetch(callbackContext, FirebaseRemoteConfig.getInstance().fetch(cacheExpirationSeconds));
}
private void fetch(final CallbackContext callbackContext, final Task<Void> task) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
task.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(Task<Void> task) {
callbackContext.success();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
callbackContext.error(e.getMessage());
}
});
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void getByteArray(final CallbackContext callbackContext, final String key, final String namespace) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
byte[] bytes = namespace == null ? FirebaseRemoteConfig.getInstance().getByteArray(key)
: FirebaseRemoteConfig.getInstance().getByteArray(key, namespace);
JSONObject object = new JSONObject();
object.put("base64", Base64.encodeToString(bytes, Base64.DEFAULT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
object.put("array", new JSONArray(bytes));
}
callbackContext.success(object);
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void getValue(final CallbackContext callbackContext, final String key, final String namespace) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
FirebaseRemoteConfigValue value = namespace == null ? FirebaseRemoteConfig.getInstance().getValue(key)
: FirebaseRemoteConfig.getInstance().getValue(key, namespace);
callbackContext.success(value.asString());
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void getInfo(final CallbackContext callbackContext) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
FirebaseRemoteConfigInfo remoteConfigInfo = FirebaseRemoteConfig.getInstance().getInfo();
JSONObject info = new JSONObject();
JSONObject settings = new JSONObject();
settings.put("developerModeEnabled", remoteConfigInfo.getConfigSettings().isDeveloperModeEnabled());
info.put("configSettings", settings);
info.put("fetchTimeMillis", remoteConfigInfo.getFetchTimeMillis());
info.put("lastFetchStatus", remoteConfigInfo.getLastFetchStatus());
callbackContext.success(info);
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void setConfigSettings(final CallbackContext callbackContext, final JSONObject config) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
boolean devMode = config.getBoolean("developerModeEnabled");
FirebaseRemoteConfigSettings.Builder settings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(devMode);
FirebaseRemoteConfig.getInstance().setConfigSettings(settings.build());
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void setDefaults(final CallbackContext callbackContext, final JSONObject defaults, final String namespace) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
if (namespace == null)
FirebaseRemoteConfig.getInstance().setDefaults(defaultsToMap(defaults));
else
FirebaseRemoteConfig.getInstance().setDefaults(defaultsToMap(defaults), namespace);
callbackContext.success();
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
}
private static Map<String, Object> defaultsToMap(JSONObject object) throws JSONException {
final Map<String, Object> map = new HashMap<String, Object>();
for (Iterator<String> keys = object.keys(); keys.hasNext(); ) {
String key = keys.next();
Object value = object.get(key);
if (value instanceof Integer) {
//setDefaults() should take Longs
value = new Long((Integer) value);
} else if (value instanceof JSONArray) {
JSONArray array = (JSONArray) value;
if (array.length() == 1 && array.get(0) instanceof String) {
//parse byte[] as Base64 String
value = Base64.decode(array.getString(0), Base64.DEFAULT);
} else {
//parse byte[] as numeric array
byte[] bytes = new byte[array.length()];
for (int i = 0; i < array.length(); i++)
bytes[i] = (byte) array.getInt(i);
value = bytes;
}
}
map.put(key, value);
}
return map;
}
}
package com.redskyit.mobile.rmcv2.firebase;
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class FirebasePluginInstanceIDService extends FirebaseInstanceIdService {
private static final String TAG = "RMCv2:FBIIDService";
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
FirebasePlugin.onTokenChanged(refreshedToken);
}
}
package com.redskyit.mobile.rmcv2.firebase;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.text.TextUtils;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
import java.util.Set;
public class FirebasePluginMessagingService extends FirebaseMessagingService {
private static final String TAG = "RMCv2:FBMService";
public FirebasePluginMessagingService() {
super();
}
@Override
public void onRebind(Intent intent) {
Log.d(TAG,"rebind " + intent);
super.onRebind(intent);
}
@Override
public void onDestroy() {
Log.d(TAG,"destroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG,"unbind " + intent);
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.d(TAG,"create");
super.onCreate();
}
@Override
public void onDeletedMessages() {
Log.d(TAG, "onDeletedMessages");
super.onDeletedMessages();
}
@Override
public void onTaskRemoved(Intent rootIntent) {
Log.d(TAG, "onTaskRemoved");
super.onTaskRemoved(rootIntent);
}
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "onMessageReceived");
// [START_EXCLUDE]
// There are two types of messages data messages and notification messages. Data messages are handled
// here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
// traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated notification is displayed.
// When the user taps on the notification they are returned to the app. Messages containing both notification
// and data payloads are treated as notification messages. The Firebase console always sends notification
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
// [END_EXCLUDE]
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
String title = null;
String text = null;
String id = remoteMessage.getMessageId();
Log.d(TAG, "From: " + remoteMessage.getFrom());
Log.d(TAG, "Remote Message ID: " + id);
// Is this a notification or a data only message
RemoteMessage.Notification notification = remoteMessage.getNotification();
Bundle data = map2bundle(remoteMessage.getData());
if (notification != null) {
// get notification details
title = notification.getTitle();
text = notification.getBody();
Log.d(TAG, "Notification Message Title: " + title);
Log.d(TAG, "Notification Message Body: " + text);
if (!TextUtils.isEmpty(text) || !TextUtils.isEmpty(title)) {
// The notification has something to display, as long as the client is
// not active, then generate a notification
if (FirebasePlugin.isPaused()) {
Log.d(TAG, "NOTIFICATION RECEIVED WHILE PAUSED, SEND NOTIFICATION");
sendNotification(id, title, text, data);
return;
}
// we have received a push message with a notification element while the client
// is in foreground, in this case we don't want to show a notification, which would
// just sit there until the user opened or dismissed it, but we want to send
// the notification as data to the client for it to deal with immediately.
Log.d(TAG, "NOTIFICATION RECEIVED WHILE NOT PAUSED, SEND AS DATA");
Bundle notify = new Bundle();
notify.putString("title",title);
notify.putString("body",text);
notify.putString("sound", notification.getSound());
notify.putString("tag", notification.getTag());
notify.putString("icon", notification.getIcon());
notify.putString("color", notification.getColor());
notify.putString("clickAction", notification.getClickAction());
data.putBundle("notification", notify);
}
}
// Wasn't a valid notification, send data to the client
if (data != null) {
title = data.getString("title");
text = data.getString("text");
Log.d(TAG, "Data Message Title: " + title);
Log.d(TAG, "Data Message Text: " + text);
// Send the data directly to the client
sendData(id, data);
}
}
// Convert a string map to a bundle
private Bundle map2bundle(Map<String,String> data) {
Bundle bundle = new Bundle();
if (null != data) {
for (String key : data.keySet()) {
bundle.putString(key, data.get(key));
}
}
return bundle;
}
private void sendData(String id, Bundle data) {
data.putBoolean("rmc.is_push", true);
data.putBoolean("rmc.is_notify", false);
Log.d(TAG, "sendData " + id + ", " + data);
Intent intent = new Intent(this, OnNotificationOpenReceiver.class);
intent.putExtras(data);
Log.d(TAG, "send broadcast " + intent);
sendBroadcast(intent);
Log.d(TAG, "done broadcast " + intent);
}
private void sendNotification(String id, String title, String messageBody, Bundle data) {
data.putBoolean("rmc.is_push", true);
data.putBoolean("rmc.is_notify", true);
Intent intent = new Intent(this, OnNotificationOpenReceiver.class);
intent.putExtras(data);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id.hashCode(), intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(getApplicationInfo().icon)
.setContentTitle(title)
.setContentText(messageBody)
.setStyle(new NotificationCompat.BigTextStyle().bigText(messageBody))
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id.hashCode(), notificationBuilder.build());
}
}
package com.redskyit.mobile.rmcv2.firebase;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;
import com.redskyit.mobile.rmcv2.firebase.FirebasePlugin;
public class OnNotificationOpenReceiver extends WakefulBroadcastReceiver {
static String TAG = "RMCv2:ONOReceiver";
// This is called when a notification (as triggered by FireBasePluginMessagingService
// onMessageReceived() when the payload contains a notification) is opened by the user.
//
// We only create a notification if the client is in background. When the client is in
// foreground, onMessageReceived() will send the message directly to the client, even if
// the payload contained a notification, in which case it sets notification true.
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "RECEIVE OPEN NOTIFICATION " + intent);
FirebasePlugin.onBroadcastReceive(context, intent);
completeWakefulIntent(intent);
Log.d(TAG, "COMPLETE NOTIFICATION " + intent);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment