package by.chemerisuk.cordova.firebase; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import androidx.core.app.NotificationManagerCompat; 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 com.google.firebase.messaging.RemoteMessage; import org.apache.cordova.CallbackContext; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.List; import java.util.Set; import by.chemerisuk.cordova.support.CordovaMethod; import by.chemerisuk.cordova.support.ReflectiveCordovaPlugin; import me.leolin.shortcutbadger.ShortcutBadger; import static androidx.core.content.ContextCompat.getSystemService; public class FirebaseMessagingPlugin extends ReflectiveCordovaPlugin { private static final String TAG = "FirebaseMessagingPlugin"; private JSONObject lastBundle; private boolean isBackground = false; private boolean forceShow = false; private CallbackContext tokenRefreshCallback; private CallbackContext foregroundCallback; private CallbackContext backgroundCallback; private static FirebaseMessagingPlugin instance; private NotificationManager notificationManager; @Override protected void pluginInitialize() { FirebaseMessagingPlugin.instance = this; notificationManager = getSystemService(cordova.getActivity(), NotificationManager.class); lastBundle = getNotificationData(cordova.getActivity().getIntent()); } @CordovaMethod private void subscribe(String topic, final CallbackContext callbackContext) { FirebaseMessaging.getInstance().subscribeToTopic(topic) .addOnCompleteListener(cordova.getActivity(), new OnCompleteListener<Void>() { @Override public void onComplete(Task<Void> task) { if (task.isSuccessful()) { callbackContext.success(); } else { callbackContext.error(task.getException().getMessage()); } } }); } @CordovaMethod private void unsubscribe(String topic, final CallbackContext callbackContext) { FirebaseMessaging.getInstance().unsubscribeFromTopic(topic) .addOnCompleteListener(cordova.getActivity(), new OnCompleteListener<Void>() { @Override public void onComplete(Task<Void> task) { if (task.isSuccessful()) { callbackContext.success(); } else { callbackContext.error(task.getException().getMessage()); } } }); } @CordovaMethod private void revokeToken(CallbackContext callbackContext) throws IOException { FirebaseInstanceId.getInstance().deleteInstanceId(); callbackContext.success(); } @CordovaMethod private void getInstanceId(final CallbackContext callbackContext) { FirebaseInstanceId.getInstance().getInstanceId() .addOnCompleteListener(cordova.getActivity(), new OnCompleteListener<InstanceIdResult>() { @Override public void onComplete(Task<InstanceIdResult> task) { if (task.isSuccessful()) { callbackContext.success(task.getResult().getId()); } else { callbackContext.error(task.getException().getMessage()); } } }); } @CordovaMethod private void getToken(String type, final CallbackContext callbackContext) { if (type != null) { callbackContext.sendPluginResult( new PluginResult(PluginResult.Status.OK, (String)null)); } else { FirebaseInstanceId.getInstance().getInstanceId() .addOnCompleteListener(cordova.getActivity(), new OnCompleteListener<InstanceIdResult>() { @Override public void onComplete(Task<InstanceIdResult> task) { if (task.isSuccessful()) { callbackContext.success(task.getResult().getToken()); } else { callbackContext.error(task.getException().getMessage()); } } }); } } @CordovaMethod private void onTokenRefresh(CallbackContext callbackContext) { instance.tokenRefreshCallback = callbackContext; } @CordovaMethod private void onMessage(CallbackContext callbackContext) { instance.foregroundCallback = callbackContext; } @CordovaMethod private void onBackgroundMessage(CallbackContext callbackContext) { instance.backgroundCallback = callbackContext; if (lastBundle != null) { sendNotification(lastBundle, callbackContext); lastBundle = null; } } @CordovaMethod private void setBadge(int value, CallbackContext callbackContext) { if (value >= 0) { Context context = cordova.getActivity().getApplicationContext(); ShortcutBadger.applyCount(context, value); callbackContext.success(); } else { callbackContext.error("Badge value can't be negative"); } } @CordovaMethod private void getBadge(CallbackContext callbackContext) { Context context = cordova.getActivity(); SharedPreferences settings = context.getSharedPreferences("badge", Context.MODE_PRIVATE); callbackContext.success(settings.getInt("badge", 0)); } @CordovaMethod private void requestPermission(JSONObject options, CallbackContext callbackContext) throws JSONException { Context context = cordova.getActivity().getApplicationContext(); this.forceShow = options.optBoolean("forceShow"); if (NotificationManagerCompat.from(context).areNotificationsEnabled()) { callbackContext.success(); } else { callbackContext.error("Notifications permission is not granted"); } } @CordovaMethod private void createChannel(JSONObject options, CallbackContext callbackContext) throws JSONException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { throw new UnsupportedOperationException("Notification channels are not supported"); } String channelId = options.getString("id"); String channelName = options.getString("name"); int importance = options.getInt("importance"); NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); channel.setDescription(options.optString("description", "")); channel.setShowBadge(options.optBoolean("badge", true)); channel.enableLights(options.optBoolean("light", false)); channel.setLightColor(options.optInt("lightColor", 0)); String soundName = options.optString("sound", "default"); if (!"default".equals(soundName)) { String packageName = cordova.getActivity().getPackageName(); Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + packageName + "/raw/" + soundName); channel.setSound(soundUri, new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .build()); } JSONArray vibrationPattern = options.optJSONArray("vibration"); if (vibrationPattern != null) { int patternLength = vibrationPattern.length(); long[] patternArray = new long[patternLength]; for (int i = 0; i < patternLength; i++) { patternArray[i] = vibrationPattern.getLong(i); } channel.setVibrationPattern(patternArray); channel.enableVibration(true); } else { channel.enableVibration(options.optBoolean("vibration", true)); } notificationManager.createNotificationChannel(channel); callbackContext.success(); } @CordovaMethod private void findChannel(String channelId, CallbackContext callbackContext) throws JSONException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { throw new UnsupportedOperationException("Notification channels are not supported"); } NotificationChannel channel = notificationManager.getNotificationChannel(channelId); if (channel == null) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, (String)null)); } else { callbackContext.success(new JSONObject() .put("id", channel.getId()) .put("name", channel.getName()) .put("description", channel.getDescription())); } } @CordovaMethod private void listChannels(CallbackContext callbackContext) throws JSONException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { throw new UnsupportedOperationException("Notification channels are not supported"); } List<NotificationChannel> channels = notificationManager.getNotificationChannels(); JSONArray result = new JSONArray(); for (NotificationChannel channel : channels) { result.put(new JSONObject() .put("id", channel.getId()) .put("name", channel.getName()) .put("description", channel.getDescription())); } callbackContext.success(result); } @CordovaMethod private void deleteChannel(String channelId, CallbackContext callbackContext) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { throw new UnsupportedOperationException("Notification channels are not supported"); } notificationManager.deleteNotificationChannel(channelId); callbackContext.success(); } @Override public void onNewIntent(Intent intent) { JSONObject notificationData = getNotificationData(intent); if (instance != null && notificationData != null) { sendNotification(notificationData, instance.backgroundCallback); } } @Override public void onPause(boolean multitasking) { this.isBackground = true; } @Override public void onResume(boolean multitasking) { this.isBackground = false; } static void sendNotification(RemoteMessage remoteMessage) { JSONObject notificationData = new JSONObject(remoteMessage.getData()); RemoteMessage.Notification notification = remoteMessage.getNotification(); try { if (notification != null) { notificationData.put("gcm", toJSON(notification)); } notificationData.put("google.message_id", remoteMessage.getMessageId()); notificationData.put("google.sent_time", remoteMessage.getSentTime()); if (instance != null) { CallbackContext callbackContext = instance.isBackground ? instance.backgroundCallback : instance.foregroundCallback; instance.sendNotification(notificationData, callbackContext); } } catch (JSONException e) { Log.e(TAG, "sendNotification", e); } } static void sendToken(String instanceId) { if (instance != null) { if (instance.tokenRefreshCallback != null && instanceId != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, instanceId); pluginResult.setKeepCallback(true); instance.tokenRefreshCallback.sendPluginResult(pluginResult); } } } static boolean isForceShow() { return instance != null && instance.forceShow; } private void sendNotification(JSONObject notificationData, CallbackContext callbackContext) { if (callbackContext != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, notificationData); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); } } private JSONObject getNotificationData(Intent intent) { Bundle bundle = intent.getExtras(); if (bundle == null) { return null; } if (!bundle.containsKey("google.message_id") && !bundle.containsKey("google.sent_time")) { return null; } try { JSONObject notificationData = new JSONObject(); Set<String> keys = bundle.keySet(); for (String key : keys) { notificationData.put(key, bundle.get(key)); } return notificationData; } catch (JSONException e) { Log.e(TAG, "getNotificationData", e); return null; } } private static JSONObject toJSON(RemoteMessage.Notification notification) throws JSONException { return new JSONObject() .put("body", notification.getBody()) .put("title", notification.getTitle()) .put("sound", notification.getSound()) .put("icon", notification.getIcon()) .put("tag", notification.getTag()) .put("color", notification.getColor()) .put("clickAction", notification.getClickAction()); } }