-
-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Referral Notifications - FCM Push Notifications #2667
Changes from all commits
244f34b
5fbcdc7
c833a92
c7ece13
934379d
c818d08
03840ac
eab4931
43dd926
3ddf08d
9656cdb
2c5472a
095650a
6f2eec2
6531282
57bee40
9558b83
065c2c9
a9a7d18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package org.commcare.services; | ||
|
||
import android.app.NotificationManager; | ||
import android.app.PendingIntent; | ||
import android.content.Intent; | ||
import android.os.Build; | ||
|
||
import androidx.core.app.NotificationCompat; | ||
|
||
import com.google.firebase.messaging.FirebaseMessagingService; | ||
import com.google.firebase.messaging.RemoteMessage; | ||
|
||
import org.commcare.CommCareNoficationManager; | ||
import org.commcare.activities.DispatchActivity; | ||
import org.commcare.dalvik.R; | ||
import org.commcare.util.LogTypes; | ||
import org.commcare.utils.FirebaseMessagingUtil; | ||
import org.javarosa.core.services.Logger; | ||
import org.joda.time.DateTime; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* This service responds to any events/messages from Firebase Cloud Messaging. The intention is to | ||
* offer an entry point for any message from FCM and trigger the necessary steps based on the action | ||
* key. | ||
*/ | ||
public class CommCareFirebaseMessagingService extends FirebaseMessagingService { | ||
|
||
private final static int FCM_NOTIFICATION = R.string.fcm_notification; | ||
private enum ActionTypes{ | ||
SYNC, | ||
INVALID | ||
} | ||
|
||
/** | ||
* Upon receiving a new message from FCM, CommCare needs to: | ||
* 1) Trigger the notification if the message contains a Notification object. Note that the | ||
* presence of a Notification object causes the onMessageReceived to not be called when the | ||
* app is in the background, which means that the data object won't be processed from here | ||
* 2) Verify if the message contains a data object and trigger the necessary steps according | ||
* to the action it carries | ||
* | ||
*/ | ||
@Override | ||
public void onMessageReceived(RemoteMessage remoteMessage) { | ||
Logger.log(LogTypes.TYPE_FCM, "Message received: " + remoteMessage.getMessageId()); | ||
Map<String, String> payloadData = remoteMessage.getData(); | ||
RemoteMessage.Notification payloadNotification = remoteMessage.getNotification(); | ||
|
||
if (payloadNotification != null) { | ||
showNotification(payloadNotification); | ||
} | ||
|
||
// Check if the message contains a data object, there is no further action if not | ||
if (payloadData.size() == 0){ | ||
return; | ||
} | ||
|
||
FCMMessageData fcmMessageData = new FCMMessageData(payloadData); | ||
|
||
switch(fcmMessageData.action){ | ||
case SYNC -> {} // trigger sync for fcmMessageData | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
default -> | ||
Logger.log(LogTypes.TYPE_FCM, "Invalid FCM action"); | ||
} | ||
} | ||
|
||
@Override | ||
public void onNewToken(String token) { | ||
// TODO: Remove the token from the log | ||
Logger.log(LogTypes.TYPE_FCM, "New registration token was generated"+token); | ||
FirebaseMessagingUtil.updateFCMToken(token); | ||
} | ||
|
||
|
||
/** | ||
* This method purpose is to show notifications to the user when the app is in the foreground. | ||
* When the app is in the background, FCM is responsible for notifying the user | ||
* | ||
*/ | ||
private void showNotification(RemoteMessage.Notification notification) { | ||
String notificationTitle = notification.getTitle(); | ||
String notificationText = notification.getBody(); | ||
NotificationManager mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); | ||
|
||
Intent i = new Intent(this, DispatchActivity.class); | ||
i.setAction(Intent.ACTION_MAIN); | ||
i.addCategory(Intent.CATEGORY_LAUNCHER); | ||
|
||
PendingIntent contentIntent; | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) | ||
contentIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE); | ||
else | ||
contentIntent = PendingIntent.getActivity(this, 0, i, 0); | ||
|
||
NotificationCompat.Builder fcmNotification = new NotificationCompat.Builder(this, | ||
CommCareNoficationManager.NOTIFICATION_CHANNEL_SERVER_COMMUNICATIONS_ID) | ||
.setContentTitle(notificationTitle) | ||
.setContentText(notificationText) | ||
.setContentIntent(contentIntent) | ||
.setSmallIcon(R.drawable.notification) | ||
.setPriority(NotificationCompat.PRIORITY_HIGH) | ||
.setWhen(System.currentTimeMillis()); | ||
|
||
mNM.notify(FCM_NOTIFICATION, fcmNotification.build()); | ||
} | ||
|
||
/** | ||
* This class is to facilitate handling the FCM Message Data object. It should contain all the | ||
* necessary checks and transformations | ||
*/ | ||
public class FCMMessageData { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: best to put this model in a new file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shubham1g5 This was done on #2680 in this commit |
||
private ActionTypes action; | ||
private String username; | ||
private String domain; | ||
private DateTime creationTime; | ||
|
||
private FCMMessageData(Map<String, String> payloadData){ | ||
this.action = getActionType(payloadData.get("action")); | ||
this.username = payloadData.get("username"); | ||
this.domain = payloadData.get("domain"); | ||
this.creationTime = convertISO8601ToDateTime(payloadData.get("created_at")); | ||
} | ||
|
||
private DateTime convertISO8601ToDateTime(String timeInISO8601) { | ||
if (timeInISO8601 == null){ | ||
return null; | ||
} | ||
return new DateTime(timeInISO8601); | ||
} | ||
|
||
private ActionTypes getActionType(String action) { | ||
if (action == null) { | ||
return ActionTypes.INVALID; | ||
} | ||
|
||
switch (action.toUpperCase()) { | ||
case "SYNC" -> { | ||
return ActionTypes.SYNC; | ||
} | ||
default -> { | ||
return ActionTypes.INVALID; | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can a notification contains both
data
andnotification
object ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. My understanding is that the main difference is when the app is in the background, the SDK doesn't call
onMessageReceived
only triggers the notification, i.e. if we want thesync
to happen regardless of whether the app is in the foreground or background, we shouldn't have anotification
object in the messageThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't completely understand. How do we handle the
sync
notifcation when the app is in background ? Do we just discard the notification ? Or it's routed thorough a different pathway ?Also when
onMessageReceived
gets called (app is in foreground), It seems like we can only have one ofnotification
ordata
payload. In that case it might make more sense to rearrange the code flow as -There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bumping
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a bit tricky. So, when the app is in the background and the message contains a
notification
object, Firebase SDK takes care of delivering the notification to the Notification Drawer. In case the message also contains adata
object, Firebase SDK adds it to the extras of the intent of the launcher activity of the app. This is the default behaviour, so when the user taps the notification it opens the app launcher with thedata
object as an extra.We can have both, but in this case because we want to trigger the sync when the app is in the background too, we are having messages with only the
data
objectThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That means we should be handling these intents on app startup right ? Or they by default gets delivered to this messaging service when user clicks on notification and pull the app in foreground ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shubham1g5 This is supposed to be handled on app startup but it's not yet implemented because our current implementation on HQ doesn't trigger FCM messages with both
notification
anddata
objects (see image below). So, the intention is to incorporate that on a later stage depending on the feedback we get as well, however, not in the scope of this work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not we still need to handle only
data
messages that gets send when app is in background ? if not, How does the app handle sync data messages that gets delivered to the phone when the CC is in background ? For ex, my current understanding is -Does it sound right to you ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the
message
doesn't contain anotification
object and the app is in the background, the message is delivered to the FCM Service, so theonMessageDelivered
is called. More about this can be found here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, thanks for confirming.