diff --git a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java index cb2b069f..e21073ee 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java +++ b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java @@ -294,9 +294,11 @@ public class DisplayNotificationTask extends AsyncTask { } private NotificationCompat.Action createAction(Bundle action, Class intentClass, Bundle notification) { + boolean runInBackground = action.containsKey("runInBackground") && action.getBoolean("runInBackground"); String actionKey = action.getString("action"); - PendingIntent actionIntent = createIntent(intentClass, notification, actionKey); - + PendingIntent actionIntent = runInBackground ? + createBroadcastIntent(notification, actionKey) : + createIntent(intentClass, notification, actionKey); int icon = getIcon(action.getString("icon")); String title = action.getString("title"); @@ -334,10 +336,21 @@ public class DisplayNotificationTask extends AsyncTask { } String notificationId = notification.getString("notificationId"); - return PendingIntent.getActivity(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); } + private PendingIntent createBroadcastIntent(Bundle notification, String action) { + Intent intent = new Intent(context, RNFirebaseBackgroundNotificationActionReceiver.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + + String notificationId = notification.getString("notificationId") + action; + + intent.setAction("io.invertase.firebase.notifications.BackgroundAction"); + intent.putExtra("action", action); + intent.putExtra("notification", notification); + return PendingIntent.getBroadcast(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + private RemoteInput createRemoteInput(Bundle remoteInput) { String resultKey = remoteInput.getString("resultKey"); diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionReceiver.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionReceiver.java new file mode 100644 index 00000000..4ac2db87 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionReceiver.java @@ -0,0 +1,50 @@ +package io.invertase.firebase.notifications; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.facebook.react.HeadlessJsTaskService; +import com.facebook.react.ReactApplication; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; + +import io.invertase.firebase.Utils; + +public class RNFirebaseBackgroundNotificationActionReceiver extends BroadcastReceiver { + static boolean isBackgroundNotficationIntent(Intent intent) { + return intent.getExtras() != null && intent.hasExtra("action") && intent.hasExtra("notification"); + } + + static WritableMap toNotificationOpenMap(Intent intent) { + Bundle extras = intent.getExtras(); + WritableMap notificationMap = Arguments.makeNativeMap(extras.getBundle("notification")); + WritableMap notificationOpenMap = Arguments.createMap(); + notificationOpenMap.putString("action", extras.getString("action")); + notificationOpenMap.putMap("notification", notificationMap); + return notificationOpenMap; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!isBackgroundNotficationIntent(intent)) { + return; + } + + if (Utils.isAppInForeground(context)) { + WritableMap notificationOpenMap = toNotificationOpenMap(intent); + + ReactApplication reactApplication = (ReactApplication)context.getApplicationContext(); + ReactContext reactContext = reactApplication.getReactNativeHost().getReactInstanceManager().getCurrentReactContext(); + + Utils.sendEvent(reactContext, "notifications_notification_opened", notificationOpenMap); + } else { + Intent serviceIntent = new Intent(context, RNFirebaseBackgroundNotificationActionsService.class); + serviceIntent.putExtras(intent.getExtras()); + context.startService(serviceIntent); + HeadlessJsTaskService.acquireWakeLockNow(context); + } + } +} diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionsService.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionsService.java new file mode 100644 index 00000000..9bfb83bb --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseBackgroundNotificationActionsService.java @@ -0,0 +1,29 @@ +package io.invertase.firebase.notifications; + +import android.content.Intent; + +import com.facebook.react.HeadlessJsTaskService; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.jstasks.HeadlessJsTaskConfig; + +import javax.annotation.Nullable; + +import static io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver.isBackgroundNotficationIntent; +import static io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver.toNotificationOpenMap; + +public class RNFirebaseBackgroundNotificationActionsService extends HeadlessJsTaskService { + @Override + protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) { + if (isBackgroundNotficationIntent(intent)) { + WritableMap notificationOpenMap = toNotificationOpenMap(intent); + + return new HeadlessJsTaskConfig( + "RNFirebaseBackgroundNotificationAction", + notificationOpenMap, + 60000, + true + ); + } + return null; + } +} diff --git a/lib/index.d.ts b/lib/index.d.ts index c7bf9fb2..ae41b750 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1277,6 +1277,7 @@ declare module 'react-native-firebase' { semanticAction?: SemanticAction; showUserInterface?: boolean; title: string; + runInBackground?: boolean; constructor(action: string, icon: string, title: string); @@ -1284,6 +1285,7 @@ declare module 'react-native-firebase' { setAllowGenerateReplies(allowGeneratedReplies: boolean): Action; setSemanticAction(semanticAction: SemanticAction): Action; setShowUserInterface(showUserInterface: boolean): Action; + setRunInBackground(runInBackground: boolean): Action; } class RemoteInput { diff --git a/lib/modules/notifications/AndroidAction.js b/lib/modules/notifications/AndroidAction.js index ed2a940c..c52f9c13 100644 --- a/lib/modules/notifications/AndroidAction.js +++ b/lib/modules/notifications/AndroidAction.js @@ -16,6 +16,7 @@ export default class AndroidAction { _semanticAction: SemanticActionType | void; _showUserInterface: boolean | void; _title: string; + _runInBackground: boolean | void; constructor(action: string, icon: string, title: string) { this._action = action; @@ -52,6 +53,10 @@ export default class AndroidAction { return this._title; } + get runInBackground(): ?boolean { + return this._runInBackground; + } + /** * * @param remoteInput @@ -102,6 +107,16 @@ export default class AndroidAction { return this; } + /** + * + * @param runInBackground + * @returns {AndroidAction} + */ + setRunInBackground(runInBackground: boolean): AndroidAction { + this._runInBackground = runInBackground + return this; + } + build(): NativeAndroidAction { if (!this._action) { throw new Error('AndroidAction: Missing required `action` property'); @@ -119,6 +134,7 @@ export default class AndroidAction { semanticAction: this._semanticAction, showUserInterface: this._showUserInterface, title: this._title, + runInBackground: this._runInBackground }; } } @@ -145,6 +161,9 @@ export const fromNativeAndroidAction = ( if (nativeAction.showUserInterface) { action.setShowUserInterface(nativeAction.showUserInterface); } + if (nativeAction.runInBackground) { + action.setRunInBackground(nativeAction.runInBackground); + } return action; }; diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index 780eabab..c94cbd7b 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -136,6 +136,7 @@ export type NativeAndroidAction = {| semanticAction?: SemanticActionType, showUserInterface?: boolean, title: string, + runInBackground?: boolean, |}; export type NativeAndroidNotification = {|