From 99b4b6550b3d654a276e811d6d686a9d5a9c2771 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 15 Feb 2018 08:11:17 +0000 Subject: [PATCH] [notifications] WIP Android implementation --- .../RNFirebaseLocalMessagingHelper.java | 272 ----------- .../RNFirebaseLocalMessagingPublisher.java | 14 - .../messaging/RNFirebaseMessaging.java | 2 +- .../RNFirebaseSystemBootEventReceiver.java | 27 -- .../RNFirebaseNotificationManager.java | 434 ++++++++++++++++++ .../RNFirebaseNotificationReceiver.java | 15 + .../RNFirebaseNotifications.java | 54 ++- ...RNFirebaseNotificationsRebootReceiver.java | 18 + .../notifications/AndroidNotification.js | 5 + 9 files changed, 524 insertions(+), 317 deletions(-) delete mode 100644 android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java delete mode 100644 android/src/main/java/io/invertase/firebase/messaging/RNFirebaseSystemBootEventReceiver.java create mode 100644 android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java create mode 100644 android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationReceiver.java create mode 100644 android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationsRebootReceiver.java diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java index cb32f218..2bfbd12b 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java @@ -43,255 +43,7 @@ public class RNFirebaseLocalMessagingHelper { } public void sendNotification(Bundle bundle) { - try { - Class intentClass = getMainActivityClass(); - if (intentClass == null) { - return; - } - if (bundle.getString("body") == null) { - return; - } - - Resources res = mContext.getResources(); - String packageName = mContext.getPackageName(); - - String title = bundle.getString("title"); - if (title == null) { - ApplicationInfo appInfo = mContext.getApplicationInfo(); - title = mContext.getPackageManager().getApplicationLabel(appInfo).toString(); - } - - NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext) - .setContentTitle(title) - .setContentText(bundle.getString("body")) - .setTicker(bundle.getString("ticker")) - .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) - .setAutoCancel(bundle.getBoolean("auto_cancel", true)) - .setNumber(bundle.getInt("number")) - .setSubText(bundle.getString("sub_text")) - .setGroup(bundle.getString("group")) - .setVibrate(new long[]{0, DEFAULT_VIBRATION}) - .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) - .setExtras(bundle.getBundle("data")); - - //priority - String priority = bundle.getString("priority", ""); - switch(priority) { - case "min": - notification.setPriority(NotificationCompat.PRIORITY_MIN); - break; - case "high": - notification.setPriority(NotificationCompat.PRIORITY_HIGH); - break; - case "max": - notification.setPriority(NotificationCompat.PRIORITY_MAX); - break; - default: - notification.setPriority(NotificationCompat.PRIORITY_DEFAULT); - } - - //icon - String smallIcon = bundle.getString("icon", "ic_launcher"); - int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName); - notification.setSmallIcon(smallIconResId); - - //large icon - String largeIcon = bundle.getString("large_icon"); - if(largeIcon != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ - if (largeIcon.startsWith("http://") || largeIcon.startsWith("https://")) { - Bitmap bitmap = getBitmapFromURL(largeIcon); - notification.setLargeIcon(bitmap); - } else { - int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName); - Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId); - - if (largeIconResId != 0) { - notification.setLargeIcon(largeIconBitmap); - } - } - } - - //big text - String bigText = bundle.getString("big_text"); - if(bigText != null){ - notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)); - } - - //sound - String soundName = bundle.getString("sound", "default"); - if (!soundName.equalsIgnoreCase("default")) { - int soundResourceId = res.getIdentifier(soundName, "raw", packageName); - if(soundResourceId == 0){ - soundName = soundName.substring(0, soundName.lastIndexOf('.')); - soundResourceId = res.getIdentifier(soundName, "raw", packageName); - } - notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId)); - } - - //color - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - notification.setCategory(NotificationCompat.CATEGORY_CALL); - - String color = bundle.getString("color"); - if (color != null) { - notification.setColor(Color.parseColor(color)); - } - } - - //vibrate - if(bundle.containsKey("vibrate")){ - long vibrate = bundle.getLong("vibrate", Math.round(bundle.getDouble("vibrate", bundle.getInt("vibrate")))); - if(vibrate > 0){ - notification.setVibrate(new long[]{0, vibrate}); - }else{ - notification.setVibrate(null); - } - } - - //lights - if (bundle.getBoolean("lights")) { - notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS); - } - - Log.d(TAG, "broadcast intent before showing notification"); - Intent i = new Intent("io.invertase.firebase.messaging.ReceiveLocalNotification"); - i.putExtras(bundle); - mContext.sendOrderedBroadcast(i, null); - - if(!mIsForeground || bundle.getBoolean("show_in_foreground")){ - Intent intent = new Intent(mContext, intentClass); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtras(bundle); - intent.setAction(bundle.getString("click_action")); - - int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis(); - PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - - NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - notification.setContentIntent(pendingIntent); - - Notification info = notification.build(); - - if (bundle.containsKey("tag")) { - String tag = bundle.getString("tag"); - notificationManager.notify(tag, notificationID, info); - } else { - notificationManager.notify(notificationID, info); - } - } - //clear out one time scheduled notification once fired - if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.remove(bundle.getString("id")); - editor.apply(); - } - } catch (Exception e) { - Log.e(TAG, "failed to send local notification", e); - } - } - - public void sendNotificationScheduled(Bundle bundle) { - Class intentClass = getMainActivityClass(); - if (intentClass == null) { - return; - } - - String notificationId = bundle.getString("id"); - if(notificationId == null){ - Log.e(TAG, "failed to schedule notification because id is missing"); - return; - } - - Long fireDate = Math.round(bundle.getDouble("fire_date")); - - if (fireDate == 0) { - Log.e(TAG, "failed to schedule notification because fire date is missing"); - return; - } - - Intent notificationIntent = new Intent(mContext, RNFirebaseLocalMessagingPublisher.class); - notificationIntent.putExtras(bundle); - PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - Long interval = null; - switch (bundle.getString("repeat_interval", "")) { - case "minute": - interval = (long) 60000; - break; - case "hour": - interval = AlarmManager.INTERVAL_HOUR; - break; - case "day": - interval = AlarmManager.INTERVAL_DAY; - break; - case "week": - interval = AlarmManager.INTERVAL_DAY * 7; - break; - } - - if(interval != null){ - getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent); - } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ - getAlarmManager().setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); - }else { - getAlarmManager().set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); - } - - //store intent - SharedPreferences.Editor editor = sharedPreferences.edit(); - try { - JSONObject json = BundleJSONConverter.convertToJSON(bundle); - editor.putString(notificationId, json.toString()); - editor.apply(); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public void cancelLocalNotification(String notificationId) { - cancelAlarm(notificationId); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.remove(notificationId); - editor.apply(); - } - - public void cancelAllLocalNotifications() { - java.util.Map keyMap = sharedPreferences.getAll(); - SharedPreferences.Editor editor = sharedPreferences.edit(); - for(java.util.Map.Entry entry:keyMap.entrySet()){ - cancelAlarm(entry.getKey()); - } - editor.clear(); - editor.apply(); - } - - public void removeDeliveredNotification(String notificationId){ - NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(notificationId.hashCode()); - } - - public void removeAllDeliveredNotifications(){ - NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancelAll(); - } - - public ArrayList getScheduledLocalNotifications(){ - ArrayList array = new ArrayList(); - java.util.Map keyMap = sharedPreferences.getAll(); - for(java.util.Map.Entry entry:keyMap.entrySet()){ - try { - JSONObject json = new JSONObject((String)entry.getValue()); - Bundle bundle = BundleJSONConverter.convertToBundle(json); - array.add(bundle); - } catch (JSONException e) { - e.printStackTrace(); - } - } - return array; } public void setApplicationForeground(boolean foreground){ @@ -309,28 +61,4 @@ public class RNFirebaseLocalMessagingHelper { return null; } } - - private AlarmManager getAlarmManager() { - return (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - } - - private void cancelAlarm(String notificationId) { - Intent notificationIntent = new Intent(mContext, RNFirebaseLocalMessagingPublisher.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); - getAlarmManager().cancel(pendingIntent); - } - - private Bitmap getBitmapFromURL(String strURL) { - try { - URL url = new URL(strURL); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setDoInput(true); - connection.connect(); - InputStream input = connection.getInputStream(); - return BitmapFactory.decodeStream(input); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } } diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java deleted file mode 100644 index 103b3168..00000000 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.invertase.firebase.messaging; - -import android.app.Application; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class RNFirebaseLocalMessagingPublisher extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - new RNFirebaseLocalMessagingHelper((Application) context.getApplicationContext()).sendNotification(intent.getExtras()); - } -} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java index 819e48ae..d87207da 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java @@ -34,7 +34,7 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A private static final String BADGE_FILE = "BadgeCountFile"; private static final String BADGE_KEY = "BadgeCount"; - private static final String TAG = RNFirebaseMessaging.class.getCanonicalName(); + private static final String TAG = "RNFirebaseMessaging"; private SharedPreferences sharedPreferences = null; diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseSystemBootEventReceiver.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseSystemBootEventReceiver.java deleted file mode 100644 index b124cde5..00000000 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseSystemBootEventReceiver.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.invertase.firebase.messaging; - -import android.app.Application; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import java.util.ArrayList; - -import android.os.Bundle; -import android.util.Log; - -/** - * Set alarms for scheduled notification after system reboot. - */ -public class RNFirebaseSystemBootEventReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Log.i("FCMSystemBootReceiver", "Received reboot event"); - RNFirebaseLocalMessagingHelper helper = new RNFirebaseLocalMessagingHelper((Application) context.getApplicationContext()); - ArrayList bundles = helper.getScheduledLocalNotifications(); - for(Bundle bundle: bundles){ - helper.sendNotificationScheduled(bundle); - } - } -} diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java new file mode 100644 index 00000000..606334b7 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -0,0 +1,434 @@ +package io.invertase.firebase.notifications; + + +import android.app.AlarmManager; +import android.app.Application; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +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 com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Map; + +import io.invertase.firebase.messaging.BundleJSONConverter; + +public class RNFirebaseNotificationManager { + private static final String PREFERENCES_KEY = "RNFNotifications"; + private static final String TAG = "RNFNotificationManager"; + private AlarmManager alarmManager; + private Context context; + private NotificationManager notificationManager; + private SharedPreferences preferences; + + public RNFirebaseNotificationManager(Context context) { + this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + this.context = context; + this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + this.preferences = context.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE); + } + + public void cancelAllNotifications() { + try { + Map notifications = preferences.getAll(); + + for(String notificationId : notifications.keySet()){ + cancelAlarm(notificationId); + } + preferences.edit().clear().apply(); + } catch (SecurityException e) { + // TODO: Identify what these situations are + // In some devices/situations cancelAllLocalNotifications can throw a SecurityException. + Log.e(TAG, e.getMessage()); + } + } + + public void cancelNotification(String notificationId) { + cancelAlarm(notificationId); + preferences.edit().remove(notificationId).apply(); + } + + public void displayNotification(Bundle notification) { + displayNotification(notification, null); + } + + public void displayNotification(ReadableMap notification, Promise promise) { + Bundle notificationBundle = Arguments.toBundle(notification); + displayNotification(notificationBundle, promise); + } + + private void displayNotification(Bundle notification, Promise promise) { + try { + Class intentClass = getMainActivityClass(); + if (intentClass == null) { + return; + } + + if (bundle.getString("body") == null) { + return; + } + + Resources res = mContext.getResources(); + String packageName = mContext.getPackageName(); + + String channelId = notification.getString("channelId"); + + NotificationCompat.Builder nb = new NotificationCompat.Builder(context, channelId); + + if (notification.containsKey("body")) { + nb = nb.setContentText(notification.getString("body")); + } + if (notification.containsKey("data")) { + nb = nb.setExtras(notification.getBundle("data")); + } + if (notification.containsKey("sound")) { + // TODO: Sound URI; + nb = nb.setSound(); + } + if (notification.containsKey("subtitle")) { + nb = nb.setSubText(notification.getString("subtitle")); + } + if (notification.containsKey("title")) { + nb = nb.setContentTitle(notification.getString("title")); + } + + if (notification.containsKey("autoCancel")) { + nb = nb.setAutoCancel(notification.getBoolean("autoCancel")); + } + if (notification.containsKey("badgeIconType")) { + nb = nb.setBadgeIconType(notification.getInt("badgeIconType")); + } + if (notification.containsKey("category")) { + nb = nb.setCategory(notification.getString("category")); + } + if (notification.containsKey("color")) { + nb = nb.setColor(notification.getInt("color")); + } + if (notification.containsKey("colorized")) { + nb = nb.setColorized(notification.getBoolean("colorized")); + } + if (notification.containsKey("contentInfo")) { + nb = nb.setContentInfo(notification.getString("contentInfo")); + } + if (notification.containsKey("defaults")) { + // TODO: Bitwise ? + nb = nb.setDefaults() + } + if (notification.containsKey("group")) { + nb = nb.setGroup(notification.getString("group")); + } + if (notification.containsKey("groupAlertBehaviour")) { + nb = nb.setGroupAlertBehavior(notification.getInt("groupAlertBehaviour")); + } + if (notification.containsKey("groupSummary")) { + nb = nb.setGroupSummary(notification.getBoolean("groupSummary")); + } + if (notification.containsKey("largeIcon")) { + Bitmap largeIcon = getBitmap(notification.getString("largeIcon")); + if (largeIcon != null) { + nb = nb.setLargeIcon(largeIcon); + } + } + if (notification.containsKey("lights")) { + Bundle lights = notification.getBundle("lights"); + nb = nb.setLights(lights.getInt("argb"), lights.getInt("onMs"), lights.getInt("offMs")); + } + if (notification.containsKey("localOnly")) { + nb = nb.setLocalOnly(notification.getBoolean("localOnly")); + } + + if (notification.containsKey("number")) { + nb = nb.setNumber(notification.getInt("number")); + } + if (notification.containsKey("ongoing")) { + nb = nb.setOngoing(notification.getBoolean("ongoing")); + } + if (notification.containsKey("onlyAlertOnce")) { + nb = nb.setOngoing(notification.getBoolean("onlyAlertOnce")); + } + if (notification.containsKey("people")) { + String[] people = notification.getStringArray("people"); + for (String person : people) { + nb = nb.addPerson(person); + } + } + if (notification.containsKey("priority")) { + nb = nb.setPriority(notification.getInt("priority")); + } + if (notification.containsKey("progress")) { + Bundle progress = notification.getBundle("lights"); + nb = nb.setProgress(progress.getInt("max"), progress.getInt("progress"), progress.getBoolean("indeterminate")); + } + if (notification.containsKey("publicVersion")) { + // TODO: Build notification + nb = nb.setPublicVersion(); + } + if (notification.containsKey("remoteInputHistory")) { + // TODO: Build notification + nb = nb.setRemoteInputHistory(notification.getStringArray("remoteInputHistory")); + } + if (notification.containsKey("shortcutId")) { + nb = nb.setShortcutId(notification.getString("shortcutId")); + } + if (notification.containsKey("showWhen")) { + nb = nb.setShowWhen(notification.getBoolean("showWhen")); + } + if (notification.containsKey("smallIcon")) { + nb = nb.setSmallIcon(notification.getInt("smallIcon")); + } + if (notification.containsKey("sortKey")) { + nb = nb.setSortKey(notification.getString("sortKey")); + } + if (notification.containsKey("ticker")) { + nb = nb.setTicker(notification.getString("ticker")); + } + if (notification.containsKey("timeoutAfter")) { + nb = nb.setTimeoutAfter(notification.getLong("timeoutAfter")); + } + if (notification.containsKey("usesChronometer")) { + nb = nb.setUsesChronometer(notification.getBoolean("usesChronometer")); + } + if (notification.containsKey("vibrate")) { + nb = nb.setVibrate(notification.getLongArray("vibrate")); + } + if (notification.containsKey("visibility")) { + nb = nb.setVisibility(notification.getInt("visibility")); + } + if (notification.containsKey("when")) { + nb = nb.setWhen(notification.getLong("when")); + } + // TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh) + // TODO: style: Style; // Need to figure out if this can work + + //icon + String smallIcon = bundle.getString("icon", "ic_launcher"); + int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName); + notification.setSmallIcon(smallIconResId); + + //big text + String bigText = bundle.getString("big_text"); + if(bigText != null){ + notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)); + } + + //sound + String soundName = bundle.getString("sound", "default"); + if (!soundName.equalsIgnoreCase("default")) { + int soundResourceId = res.getIdentifier(soundName, "raw", packageName); + if(soundResourceId == 0){ + soundName = soundName.substring(0, soundName.lastIndexOf('.')); + soundResourceId = res.getIdentifier(soundName, "raw", packageName); + } + notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId)); + } + + //color + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + String color = bundle.getString("color"); + if (color != null) { + notification.setColor(Color.parseColor(color)); + } + } + + //lights + if (bundle.getBoolean("lights")) { + notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS); + } + + Log.d(TAG, "broadcast intent before showing notification"); + Intent i = new Intent("io.invertase.firebase.messaging.ReceiveLocalNotification"); + i.putExtras(bundle); + mContext.sendOrderedBroadcast(i, null); + + if(!mIsForeground || bundle.getBoolean("show_in_foreground")){ + Intent intent = new Intent(mContext, intentClass); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtras(bundle); + intent.setAction(bundle.getString("click_action")); + + int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis(); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationManager notificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + + notification.setContentIntent(pendingIntent); + + Notification info = notification.build(); + + if (bundle.containsKey("tag")) { + String tag = bundle.getString("tag"); + notificationManager.notify(tag, notificationID, info); + } else { + notificationManager.notify(notificationID, info); + } + } + //clear out one time scheduled notification once fired + if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(bundle.getString("id")); + editor.apply(); + } + } catch (Exception e) { + Log.e(TAG, "failed to send local notification", e); + } + } + + public ArrayList getScheduledNotifications(){ + ArrayList array = new ArrayList<>(); + + Map notifications = preferences.getAll(); + + for(String notificationId : notifications.keySet()){ + try { + JSONObject json = new JSONObject((String)notifications.get(notificationId)); + Bundle bundle = BundleJSONConverter.convertToBundle(json); + array.add(bundle); + } catch (JSONException e) { + Log.e(TAG, e.getMessage()); + } + } + return array; + } + + public void removeAllDeliveredNotifications() { + notificationManager.cancelAll(); + } + + public void removeDeliveredNotification(String notificationId) { + notificationManager.cancel(notificationId.hashCode()); + } + + + public void rescheduleNotifications() { + ArrayList bundles = getScheduledNotifications(); + for(Bundle bundle: bundles){ + scheduleNotification(bundle, null); + } + } + + public void scheduleNotification(ReadableMap notification, ReadableMap schedule, Promise promise) { + Bundle notificationBundle = Arguments.toBundle(notification); + + scheduleNotification(notificationBundle, promise); + } + + private void scheduleNotification(Bundle notification, Promise promise) { + // TODO + String notificationId = notification.getString("notificationId"); + if (!notification.containsKey("notificationId")) { + if (promise != null) { + promise.reject("notification/schedule_notification_error", "Missing notificationId"); + } else { + Log.e(TAG, "Missing notificationId"); + } + return; + } + + // TODO: Schedule check + if (!notification.hasKey("schedule")) { + + return; + } + /* + Long fireDate = Math.round(bundle.getDouble("fire_date")); + + if (fireDate == 0) { + Log.e(TAG, "failed to schedule notification because fire date is missing"); + return; + }*/ + + + // Scheduled alarms are cleared on restart + // We store them so that they can be re-scheduled when the phone restarts in RNFirebaseNotificationsRebootReceiver + try { + JSONObject json = BundleJSONConverter.convertToJSON(notification); + preferences.edit().putString(notificationId, json.toString()).apply(); + } catch (JSONException e) { + promise.reject("notification/schedule_notification_error", "Failed to store notification", e); + return; + } + + + Intent notificationIntent = new Intent(context, RNFirebaseNotificationReceiver.class); + notificationIntent.putExtras(notification); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + // TODO: Scheduling + Long interval = null; + switch (notification.getString("repeat_interval", "")) { + case "minute": + interval = (long) 60000; + break; + case "hour": + interval = AlarmManager.INTERVAL_HOUR; + break; + case "day": + interval = AlarmManager.INTERVAL_DAY; + break; + case "week": + interval = AlarmManager.INTERVAL_DAY * 7; + break; + } + + if(interval != null){ + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent); + } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ + alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); + }else { + alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); + } + } + + private void cancelAlarm(String notificationId) { + Intent notificationIntent = new Intent(context, RNFirebaseNotificationManager.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.cancel(pendingIntent); + } + + private Bitmap getBitmap(String image) { + if (image.startsWith("http://") || image.startsWith("https://")) { + return getBitmapFromUrl(image); + } else { + int largeIconResId = res.getIdentifier(image, "mipmap", packageName); + return BitmapFactory.decodeResource(res, largeIconResId); + } + } + + private Bitmap getBitmapFromUrl(String imageUrl) { + try { + HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection(); + connection.setDoInput(true); + connection.connect(); + return BitmapFactory.decodeStream(connection.getInputStream()); + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + return null; + } + } +} diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationReceiver.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationReceiver.java new file mode 100644 index 00000000..50ff4133 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationReceiver.java @@ -0,0 +1,15 @@ +package io.invertase.firebase.notifications; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/* + * This is invoked by the Alarm Manager when it is time to display a scheduled notification. + */ +public class RNFirebaseNotificationReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + new RNFirebaseNotificationManager(context).displayNotification(intent.getExtras()); + } +} diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 5aee7f17..230b486a 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -1,15 +1,24 @@ package io.invertase.firebase.notifications; +import android.os.Bundle; import android.support.v4.app.NotificationCompat; +import android.util.Log; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; + +import java.util.ArrayList; public class RNFirebaseNotifications extends ReactContextBaseJavaModule { + private RNFirebaseNotificationManager notificationManager; public RNFirebaseNotifications(ReactApplicationContext context) { super(context); + notificationManager = new RNFirebaseNotificationManager(context.getApplicationContext()); } @Override @@ -18,8 +27,47 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule { } @ReactMethod - public void sendNotification(Promise promise) { - // - NotificationCompat.Builder builder = new NotificationCompat.Builder() + public void cancelAllNotifications() { + notificationManager.cancelAllNotifications(); + } + + @ReactMethod + public void cancelNotification(String notificationId) { + notificationManager.cancelNotification(notificationId); + } + + @ReactMethod + public void displayNotification(ReadableMap notification, Promise promise) { + notificationManager.displayNotification(notification, promise); + } + + @ReactMethod + public void getInitialNotification(Promise promise) { + // TODO + } + + @ReactMethod + public void getScheduledNotifications(Promise promise) { + ArrayList bundles = notificationManager.getScheduledNotifications(); + WritableArray array = Arguments.createArray(); + for (Bundle bundle : bundles) { + array.pushMap(parseNotificationBundle(bundle)); + } + promise.resolve(array); + } + + @ReactMethod + public void removeAllDeliveredNotifications() { + notificationManager.removeAllDeliveredNotifications(); + } + + @ReactMethod + public void removeDeliveredNotification(String notificationId) { + notificationManager.removeDeliveredNotification(notificationId); + } + + @ReactMethod + public void scheduleNotification(ReadableMap notification, ReadableMap schedule, Promise promise) { + notificationManager.scheduleNotification(notification, schedule, promise); } } diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationsRebootReceiver.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationsRebootReceiver.java new file mode 100644 index 00000000..80e6750f --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationsRebootReceiver.java @@ -0,0 +1,18 @@ +package io.invertase.firebase.notifications; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/* + * This is invoked when the phone restarts to ensure that all notifications are rescheduled + * correctly, as Android removes all scheduled alarms when the phone shuts down. + */ +public class RNFirebaseNotificationsRebootReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.i("RNFNotifRebootReceiver", "Received reboot event"); + new RNFirebaseNotificationManager(context).rescheduleNotifications(); + } +} diff --git a/lib/modules/notifications/AndroidNotification.js b/lib/modules/notifications/AndroidNotification.js index 12ed4331..104e13da 100644 --- a/lib/modules/notifications/AndroidNotification.js +++ b/lib/modules/notifications/AndroidNotification.js @@ -505,6 +505,11 @@ export default class AndroidNotification { build(): NativeAndroidNotification { // TODO: Validation + if (!this._channelId) { + throw new Error( + 'AndroidNotification: Missing required `channelId` property' + ); + } return { // TODO actions: Action[],