From 57ffa9bd3e4f19c57e633297bee03baa8a6999da Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Wed, 7 Mar 2018 18:29:53 +0000 Subject: [PATCH] [notifications] Fix some android issues with local notifications --- .../java/io/invertase/firebase/Utils.java | 24 ++++++++ .../RNFirebaseNotificationManager.java | 55 ++++++++++++------- .../RNFirebaseNotifications.java | 52 ++++++++---------- .../notifications/AndroidNotification.js | 10 ++++ lib/modules/notifications/index.js | 16 ++++-- .../android/app/src/main/AndroidManifest.xml | 10 ++++ tests/src/firebase.js | 9 ++- 7 files changed, 119 insertions(+), 57 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 69c82a75..cf05b9be 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -1,5 +1,7 @@ package io.invertase.firebase; +import android.app.ActivityManager; +import android.content.Context; import android.support.annotation.Nullable; import android.util.Log; @@ -522,4 +524,26 @@ public class Utils { } return deconstructedList; } + + public static boolean isAppInForeground(Context context) { + /** + We need to check if app is in foreground otherwise the app will crash. + http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not + **/ + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = + activityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return false; + } + final String packageName = context.getPackageName(); + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.importance == + ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && + appProcess.processName.equals(packageName)) { + return true; + } + } + return false; + } } diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index b99be159..310f63a9 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -23,6 +23,7 @@ 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.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -36,6 +37,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import io.invertase.firebase.Utils; import io.invertase.firebase.messaging.BundleJSONConverter; public class RNFirebaseNotificationManager { @@ -44,10 +46,15 @@ public class RNFirebaseNotificationManager { private static final String TAG = "RNFNotificationManager"; private AlarmManager alarmManager; private Context context; - private boolean isForeground = false; + private ReactApplicationContext reactContext; private NotificationManager notificationManager; private SharedPreferences preferences; + public RNFirebaseNotificationManager(ReactApplicationContext reactContext) { + this(reactContext.getApplicationContext()); + this.reactContext = reactContext; + } + public RNFirebaseNotificationManager(Context context) { this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); this.context = context; @@ -117,11 +124,6 @@ public class RNFirebaseNotificationManager { } public void displayScheduledNotification(Bundle notification) { - // Broadcast the notification to the RN Application - Intent scheduledNotificationEvent = new Intent(SCHEDULED_NOTIFICATION_EVENT); - scheduledNotificationEvent.putExtra("notification", notification); - LocalBroadcastManager.getInstance(context).sendBroadcast(scheduledNotificationEvent); - // If this isn't a repeated notification, clear it from the scheduled notifications list if (!notification.getBundle("schedule").containsKey("repeated") || !notification.getBundle("schedule").getBoolean("repeated")) { @@ -129,9 +131,14 @@ public class RNFirebaseNotificationManager { preferences.edit().remove(notificationId).apply();; } - // If the app isn't in the foreground, then we display it - // Otherwise, it is up to the JS to decide whether to send the notification - if (!isForeground) { + if (Utils.isAppInForeground(context)) { + // If the app is in the foregound, broadcast the notification to the RN Application + // It is up to the JS to decide whether to display the notification + Intent scheduledNotificationEvent = new Intent(SCHEDULED_NOTIFICATION_EVENT); + scheduledNotificationEvent.putExtra("notification", notification); + LocalBroadcastManager.getInstance(context).sendBroadcast(scheduledNotificationEvent); + } else { + // If the app is in the background, then we display it automatically displayNotification(notification, null); } } @@ -175,10 +182,6 @@ public class RNFirebaseNotificationManager { scheduleNotification(notificationBundle, promise); } - public void setIsForeground(boolean isForeground) { - this.isForeground = isForeground; - } - private void cancelAlarm(String notificationId) { Intent notificationIntent = new Intent(context, RNFirebaseNotificationManager.class); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); @@ -201,7 +204,6 @@ public class RNFirebaseNotificationManager { String notificationId = notification.getString("notificationId"); NotificationCompat.Builder nb; - // TODO: Change 27 to 'Build.VERSION_CODES.O_MR1' when using appsupport v27 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { nb = new NotificationCompat.Builder(context, channelId); } else { @@ -343,20 +345,27 @@ public class RNFirebaseNotificationManager { nb = nb.setTicker(android.getString("ticker")); } if (android.containsKey("timeoutAfter")) { - nb = nb.setTimeoutAfter(android.getLong("timeoutAfter")); + Double timeoutAfter = android.getDouble("timeoutAfter"); + nb = nb.setTimeoutAfter(timeoutAfter.longValue()); } if (android.containsKey("usesChronometer")) { nb = nb.setUsesChronometer(android.getBoolean("usesChronometer")); } if (android.containsKey("vibrate")) { - nb = nb.setVibrate(android.getLongArray("vibrate")); + double[] vibrate = android.getDoubleArray("vibrate"); + long[] vibrateArray = new long[vibrate.length]; + for (int i = 0; i < vibrate.length; i++) { + vibrateArray[i] = ((Double)vibrate[i]).longValue(); + } + nb = nb.setVibrate(vibrateArray); } if (android.containsKey("visibility")) { Double visibility = android.getDouble("visibility"); nb = nb.setVisibility(visibility.intValue()); } if (android.containsKey("when")) { - nb = nb.setWhen(android.getLong("when")); + Double when = android.getDouble("when"); + nb = nb.setWhen(when.longValue()); } // TODO: Big text / Big picture @@ -393,6 +402,10 @@ public class RNFirebaseNotificationManager { // Build the notification and send it Notification builtNotification = nb.build(); notificationManager.notify(notificationId.hashCode(), builtNotification); + + if (reactContext != null) { + Utils.sendEvent(reactContext, "notifications_notification_displayed", Arguments.fromBundle(notification)); + } } catch (Exception e) { if (promise == null) { Log.e(TAG, "Failed to send notification", e); @@ -525,7 +538,7 @@ public class RNFirebaseNotificationManager { String notificationId = notification.getString("notificationId"); Bundle schedule = notification.getBundle("schedule"); - long fireDate = schedule.getLong("fireDate"); + Double fireDate = schedule.getDouble("fireDate"); // Scheduled alarms are cleared on restart // We store them so that they can be re-scheduled when the phone restarts in RNFirebaseNotificationsRebootReceiver @@ -567,14 +580,14 @@ public class RNFirebaseNotificationManager { return; } - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent); + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate.longValue(), interval, pendingIntent); } else { if (schedule.containsKey("exact") && schedule.getBoolean("exact") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent); } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); + alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent); } } 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 2e9de173..879be98a 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -30,7 +30,7 @@ import io.invertase.firebase.Utils; import io.invertase.firebase.messaging.RNFirebaseMessagingService; import me.leolin.shortcutbadger.ShortcutBadger; -public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener { +public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener { private static final String BADGE_FILE = "BadgeCountFile"; private static final String BADGE_KEY = "BadgeCount"; private static final String TAG = "RNFirebaseNotifications"; @@ -41,9 +41,8 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen public RNFirebaseNotifications(ReactApplicationContext context) { super(context); context.addActivityEventListener(this); - context.addLifecycleEventListener(this); - notificationManager = new RNFirebaseNotificationManager(context.getApplicationContext()); + notificationManager = new RNFirebaseNotificationManager(context); sharedPreferences = context.getSharedPreferences(BADGE_FILE, Context.MODE_PRIVATE); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); @@ -86,13 +85,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen @ReactMethod public void getInitialNotification(Promise promise) { - // TODO - if (getCurrentActivity() == null) { - promise.resolve(null); - } else { - WritableMap notificationOpenMap = parseIntentForRemoteNotification(getCurrentActivity().getIntent()); - promise.resolve(notificationOpenMap); + WritableMap notificationOpenMap = null; + if (getCurrentActivity() != null) { + notificationOpenMap = parseIntentForNotification(getCurrentActivity().getIntent()); } + promise.resolve(notificationOpenMap); } @ReactMethod @@ -173,37 +170,36 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen @Override public void onNewIntent(Intent intent) { - WritableMap notificationOpenMap = parseIntentForRemoteNotification(intent); + WritableMap notificationOpenMap = parseIntentForNotification(intent); if (notificationOpenMap != null) { - Log.d(TAG, "onNewIntent called with new remote notification"); Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenMap); } } + ////////////////////////////////////////////////////////////////////// // End ActivityEventListener methods ////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - // Start LifecycleEventListener methods - ////////////////////////////////////////////////////////////////////// - @Override - public void onHostResume() { - notificationManager.setIsForeground(true); + private WritableMap parseIntentForNotification(Intent intent) { + WritableMap notificationOpenMap = parseIntentForRemoteNotification(intent); + if (notificationOpenMap == null) { + notificationOpenMap = parseIntentForLocalNotification(intent); + } + return notificationOpenMap; } - @Override - public void onHostPause() { - notificationManager.setIsForeground(false); - } + private WritableMap parseIntentForLocalNotification(Intent intent) { + if (intent.getExtras() == null || !intent.hasExtra("notificationId")) { + return null; + } - @Override - public void onHostDestroy() { - // Do nothing - } - ////////////////////////////////////////////////////////////////////// - // End LifecycleEventListener methods - ////////////////////////////////////////////////////////////////////// + WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras()); + WritableMap notificationOpenMap = Arguments.createMap(); + notificationOpenMap.putString("action", intent.getAction()); + notificationOpenMap.putMap("notification", notificationMap); + return notificationOpenMap; + } private WritableMap parseIntentForRemoteNotification(Intent intent) { // Check if FCM data exists diff --git a/lib/modules/notifications/AndroidNotification.js b/lib/modules/notifications/AndroidNotification.js index 2b33d4d2..4fadb223 100644 --- a/lib/modules/notifications/AndroidNotification.js +++ b/lib/modules/notifications/AndroidNotification.js @@ -293,6 +293,16 @@ export default class AndroidNotification { return this._notification; } + /** + * + * @param clickAction + * @returns {Notification} + */ + setClickAction(clickAction: string): Notification { + this._clickAction = clickAction; + return this._notification; + } + /** * * @param color diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 3f862a31..662d137a 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -38,7 +38,7 @@ type OnNotificationObserver = { type OnNotificationOpened = NotificationOpen => any; type OnNotificationOpenedObserver = { - next: OnNotificationOpen, + next: NotificationOpen, }; const NATIVE_EVENTS = [ @@ -158,12 +158,18 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).getBadge(); } - getInitialNotification(): Promise { + getInitialNotification(): Promise { return getNativeModule(this) .getInitialNotification() - .then( - notification => (notification ? new Notification(notification) : null) - ); + .then((notificationOpen: NativeNotificationOpen) => { + if (notificationOpen) { + return { + action: notificationOpen.action, + notification: new Notification(notificationOpen.notification), + }; + } + return null; + }); } /** diff --git a/tests/android/app/src/main/AndroidManifest.xml b/tests/android/app/src/main/AndroidManifest.xml index 0d75e277..aa4f2cd3 100644 --- a/tests/android/app/src/main/AndroidManifest.xml +++ b/tests/android/app/src/main/AndroidManifest.xml @@ -30,6 +30,16 @@ + + + + + + + + + + { notification .setTitle('Test title') .setBody('Test body') + .setNotificationId('displayed') .android.setChannelId('test') + .android.setClickAction('action') .android.setPriority(RNfirebase.notifications.Android.Priority.Max); const date = new Date(); date.setMinutes(date.getMinutes() + 1); setTimeout(() => { RNfirebase.notifications().displayNotification(notification); + notification.setNotificationId('scheduled'); + RNfirebase.notifications().scheduleNotification(notification, { + fireDate: date.getTime(), + }); }, 5); - RNfirebase.notifications().scheduleNotification(notification, { - fireDate: date.getTime(), - }); } catch (error) { console.error('messaging init error:', error); }