Skip to content

Commit

Permalink
Implement handling of Android actions in background
Browse files Browse the repository at this point in the history
There are some cases when local notification action should be handled in
background eg. snoozing the reminder. In case of it launching app UI is
not necessary and would be confusing for the end user.

Therefore there should be a way to handle local notification action in
background.

For this reason new property 'runInBackground' was added to the
AndroidAction class and TypeScript type.

Also new broadcast receiver and service were implemented to handle
properly background actions.

In order to run particular action in background user need to set its
'runInBackground' property to 'true', eg:

  ...
  .android.addAction(new firebase.notifications.Android.Action("snooze",
  "ic_snooze", "Snooze").setRunInBackground(true))

Then create a background asynchronous handler:

  const handleAsyncTask = async (notificationOpen: NotifficationOpen) => {
    if (notificationOpen && notificationOpen.notification) {
      const action = notificationOpen.action;
      const notificationId = notificationOpen.notification.notificationId;
      if (action === "snooze") {
        console.log("Reschedule notification for later time", notificationId);
      } else {
        console.log("unsupported action", action);
      }
      // hide the notification
      firebase.notifications().removeDeliveredNotification(notificationId);
    }
  }

Next hander should be registered to an headless handler:

  AppRegistry.registerHeadlessTask('RNFirebaseBackgroundNotification', () => handleAsyncTask);

Finally AndroidManifest.xml file must be modified, to include receiver
and service definition:

  <receiver
      android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationReceiver"
      android:exported="true">
    <intent-filter>
      <action android:name="io.invertase.firebase.notifications.BackgroundAction"/>
    </intent-filter>
  </receiver>
  <service android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationsService"/>

Now when ever 'Snooze' action is pressed it will launch
'handleAsyncTask' function in the background and reschedule the
notification for the later time.
  • Loading branch information
dluksza committed May 11, 2018
1 parent 6848044 commit fea93ed
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,11 @@ protected Void doInBackground(Void... voids) {
}

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");

Expand Down Expand Up @@ -334,10 +336,21 @@ private PendingIntent createIntent(Class intentClass, Bundle notification, Strin
}

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, RNFirebaseBackgroundNotificationReceiver.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");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.invertase.firebase.notifications;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.facebook.react.HeadlessJsTaskService;

import java.util.List;

public class RNFirebaseBackgroundNotificationReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, RNFirebaseBackgroundNotificationsService.class);
serviceIntent.putExtras(intent.getExtras());
context.startService(serviceIntent);

if (!isAppOnForeground((context))) {
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}

private boolean isAppOnForeground(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<ActivityManager.RunningAppProcessInfo> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.invertase.firebase.notifications;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.google.firebase.messaging.RemoteMessage;

import javax.annotation.Nullable;

import io.invertase.firebase.messaging.MessagingSerializer;

public class RNFirebaseBackgroundNotificationsService extends HeadlessJsTaskService {
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras());
return new HeadlessJsTaskConfig(
"RNFirebaseBackgroundNotification",
notificationMap,
60000,
true
);
}
return null;
}
}
2 changes: 2 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,13 +1255,15 @@ declare module 'react-native-firebase' {
semanticAction?: SemanticAction;
showUserInterface?: boolean;
title: string;
runInBackground?: boolean;

constructor(action: string, icon: string, title: string);

addRemoteInput(remoteInput: RemoteInput): Action;
setAllowGenerateReplies(allowGeneratedReplies: boolean): Action;
setSemanticAction(semanticAction: SemanticAction): Action;
setShowUserInterface(showUserInterface: boolean): Action;
setRunInBackground(runInBackground: boolean): Action;
}

class RemoteInput {
Expand Down
19 changes: 19 additions & 0 deletions lib/modules/notifications/AndroidAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +53,10 @@ export default class AndroidAction {
return this._title;
}

get runInBackground(): ?boolean {
return this._runInBackground;
}

/**
*
* @param remoteInput
Expand Down Expand Up @@ -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');
Expand All @@ -119,6 +134,7 @@ export default class AndroidAction {
semanticAction: this._semanticAction,
showUserInterface: this._showUserInterface,
title: this._title,
runInBackground: this._runInBackground
};
}
}
Expand All @@ -145,6 +161,9 @@ export const fromNativeAndroidAction = (
if (nativeAction.showUserInterface) {
action.setShowUserInterface(nativeAction.showUserInterface);
}
if (nativeAction.runInBackground) {
action.setRunInBackground(nativeAction.runInBackground);
}

return action;
};
1 change: 1 addition & 0 deletions lib/modules/notifications/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type NativeAndroidAction = {|
semanticAction?: SemanticActionType,
showUserInterface?: boolean,
title: string,
runInBackground?: boolean,
|};

export type NativeAndroidNotification = {|
Expand Down

0 comments on commit fea93ed

Please sign in to comment.