Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void onCreate() {
OSNotification notification = notificationReceivedEvent.getNotification();
JSONObject data = notification.getAdditionalData();

notificationReceivedEvent.complete(null);
notificationReceivedEvent.complete(notification);
});

OneSignal.unsubscribeWhenNotificationsAreDisabled(true);
Expand Down
1 change: 1 addition & 0 deletions OneSignalSDK/onesignal/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<activity
android:name="com.onesignal.NotificationOpenedReceiver"
android:noHistory="true"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,25 +127,10 @@ private static CharSequence getTitle(JSONObject fcmJson) {
return currentContext.getPackageManager().getApplicationLabel(currentContext.getApplicationInfo());
}


/**
* Notification delete is processed by Broadcast Receiver to avoid creation of activities that can end
* on weird UI interaction
*/
private static PendingIntent getNewActionPendingIntent(int requestCode, Intent intent) {
return PendingIntent.getActivity(currentContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

private static PendingIntent getNewDismissActionPendingIntent(int requestCode, Intent intent) {
return PendingIntent.getBroadcast(currentContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

private static Intent getNewBaseIntent(int notificationId) {
return new Intent(currentContext, notificationOpenedClass)
.putExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notificationId)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}

private static Intent getNewBaseDismissIntent(int notificationId) {
return new Intent(currentContext, notificationDismissedClass)
.putExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notificationId)
Expand Down Expand Up @@ -274,6 +259,11 @@ private static boolean showNotification(OSNotificationGenerationJob notification
JSONObject fcmJson = notificationJob.getJsonPayload();
String group = fcmJson.optString("grp", null);

GenerateNotificationOpenIntent intentGenerator = GenerateNotificationOpenIntentFromPushPayload.INSTANCE.create(
currentContext,
fcmJson
);

ArrayList<StatusBarNotification> grouplessNotifs = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
/* Android 7.0 auto groups 4 or more notifications so we find these groupless active
Expand All @@ -289,7 +279,13 @@ private static boolean showNotification(OSNotificationGenerationJob notification
OneSignalNotificationBuilder oneSignalNotificationBuilder = getBaseOneSignalNotificationBuilder(notificationJob);
NotificationCompat.Builder notifBuilder = oneSignalNotificationBuilder.compatBuilder;

addNotificationActionButtons(fcmJson, notifBuilder, notificationId, null);
addNotificationActionButtons(
fcmJson,
intentGenerator,
notifBuilder,
notificationId,
null
);

try {
addBackgroundImage(fcmJson, notifBuilder);
Expand All @@ -310,17 +306,33 @@ private static boolean showNotification(OSNotificationGenerationJob notification

Notification notification;
if (group != null) {
createGenericPendingIntentsForGroup(notifBuilder, fcmJson, group, notificationId);
createGenericPendingIntentsForGroup(
notifBuilder,
intentGenerator,
fcmJson,
group,
notificationId
);
notification = createSingleNotificationBeforeSummaryBuilder(notificationJob, notifBuilder);

// Create PendingIntents for notifications in a groupless or defined summary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
group.equals(OneSignalNotificationManager.getGrouplessSummaryKey()))
createGrouplessSummaryNotification(notificationJob, grouplessNotifs.size() + 1);
group.equals(OneSignalNotificationManager.getGrouplessSummaryKey())) {
createGrouplessSummaryNotification(
notificationJob,
intentGenerator,
grouplessNotifs.size() + 1
);
}
else
createSummaryNotification(notificationJob, oneSignalNotificationBuilder);
} else {
notification = createGenericPendingIntentsForNotif(notifBuilder, fcmJson, notificationId);
notification = createGenericPendingIntentsForNotif(
notifBuilder,
intentGenerator,
fcmJson,
notificationId
);
}
// NotificationManagerCompat does not auto omit the individual notification on the device when using
// stacked notifications on Android 4.2 and older
Expand All @@ -338,18 +350,35 @@ private static boolean showNotification(OSNotificationGenerationJob notification
return true;
}

private static Notification createGenericPendingIntentsForNotif(NotificationCompat.Builder notifBuilder, JSONObject gcmBundle, int notificationId) {
private static Notification createGenericPendingIntentsForNotif(
NotificationCompat.Builder notifBuilder,
GenerateNotificationOpenIntent intentGenerator,
JSONObject gcmBundle,
int notificationId
) {
Random random = new SecureRandom();
PendingIntent contentIntent = getNewActionPendingIntent(random.nextInt(), getNewBaseIntent(notificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, gcmBundle.toString()));
PendingIntent contentIntent = intentGenerator.getNewActionPendingIntent(
random.nextInt(),
intentGenerator.getNewBaseIntent(notificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, gcmBundle.toString())
);
notifBuilder.setContentIntent(contentIntent);
PendingIntent deleteIntent = getNewDismissActionPendingIntent(random.nextInt(), getNewBaseDismissIntent(notificationId));
notifBuilder.setDeleteIntent(deleteIntent);
return notifBuilder.build();
}

private static void createGenericPendingIntentsForGroup(NotificationCompat.Builder notifBuilder, JSONObject gcmBundle, String group, int notificationId) {
private static void createGenericPendingIntentsForGroup(
NotificationCompat.Builder notifBuilder,
GenerateNotificationOpenIntent intentGenerator,
JSONObject gcmBundle,
String group,
int notificationId
) {
Random random = new SecureRandom();
PendingIntent contentIntent = getNewActionPendingIntent(random.nextInt(), getNewBaseIntent(notificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, gcmBundle.toString()).putExtra("grp", group));
PendingIntent contentIntent = intentGenerator.getNewActionPendingIntent(
random.nextInt(),
intentGenerator.getNewBaseIntent(notificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, gcmBundle.toString()).putExtra("grp", group)
);
notifBuilder.setContentIntent(contentIntent);
PendingIntent deleteIntent = getNewDismissActionPendingIntent(random.nextInt(), getNewBaseDismissIntent(notificationId).putExtra("grp", group));
notifBuilder.setDeleteIntent(deleteIntent);
Expand Down Expand Up @@ -450,6 +479,10 @@ static void updateSummaryNotification(OSNotificationGenerationJob notificationJo
private static void createSummaryNotification(OSNotificationGenerationJob notificationJob, OneSignalNotificationBuilder notifBuilder) {
boolean updateSummary = notificationJob.isRestoring();
JSONObject fcmJson = notificationJob.getJsonPayload();
GenerateNotificationOpenIntent intentGenerator = GenerateNotificationOpenIntentFromPushPayload.INSTANCE.create(
currentContext,
fcmJson
);

String group = fcmJson.optString("grp", null);

Expand Down Expand Up @@ -536,7 +569,10 @@ private static void createSummaryNotification(OSNotificationGenerationJob notifi
createSummaryIdDatabaseEntry(dbHelper, group, summaryNotificationId);
}

PendingIntent summaryContentIntent = getNewActionPendingIntent(random.nextInt(), createBaseSummaryIntent(summaryNotificationId, fcmJson, group));
PendingIntent summaryContentIntent = intentGenerator.getNewActionPendingIntent(
random.nextInt(),
createBaseSummaryIntent(summaryNotificationId, intentGenerator, fcmJson, group)
);

// 2 or more notifications with a group received, group them together as a single notification.
if (summaryList != null &&
Expand Down Expand Up @@ -622,7 +658,13 @@ private static void createSummaryNotification(OSNotificationGenerationJob notifi
// extender setup all the settings will carry over.
// Note: However their buttons will not carry over as we need to be setup with this new summaryNotificationId.
summaryBuilder.mActions.clear();
addNotificationActionButtons(fcmJson, summaryBuilder, summaryNotificationId, group);
addNotificationActionButtons(
fcmJson,
intentGenerator,
summaryBuilder,
summaryNotificationId,
group
);

summaryBuilder.setContentIntent(summaryContentIntent)
.setDeleteIntent(summaryDeleteIntent)
Expand All @@ -646,7 +688,11 @@ private static void createSummaryNotification(OSNotificationGenerationJob notifi
}

@RequiresApi(api = Build.VERSION_CODES.M)
private static void createGrouplessSummaryNotification(OSNotificationGenerationJob notificationJob, int grouplessNotifCount) {
private static void createGrouplessSummaryNotification(
OSNotificationGenerationJob notificationJob,
GenerateNotificationOpenIntent intentGenerator,
int grouplessNotifCount
) {
JSONObject fcmJson = notificationJob.getJsonPayload();

Notification summaryNotification;
Expand All @@ -656,7 +702,10 @@ private static void createGrouplessSummaryNotification(OSNotificationGenerationJ
String summaryMessage = grouplessNotifCount + " new messages";
int summaryNotificationId = OneSignalNotificationManager.getGrouplessSummaryId();

PendingIntent summaryContentIntent = getNewActionPendingIntent(random.nextInt(), createBaseSummaryIntent(summaryNotificationId, fcmJson, group));
PendingIntent summaryContentIntent = intentGenerator.getNewActionPendingIntent(
random.nextInt(),
createBaseSummaryIntent(summaryNotificationId,intentGenerator, fcmJson, group)
);
PendingIntent summaryDeleteIntent = getNewDismissActionPendingIntent(random.nextInt(), getNewBaseDismissIntent(0).putExtra("summary", group));

NotificationCompat.Builder summaryBuilder = getBaseOneSignalNotificationBuilder(notificationJob).compatBuilder;
Expand Down Expand Up @@ -696,8 +745,13 @@ private static void createGrouplessSummaryNotification(OSNotificationGenerationJ
NotificationManagerCompat.from(currentContext).notify(summaryNotificationId, summaryNotification);
}

private static Intent createBaseSummaryIntent(int summaryNotificationId, JSONObject fcmJson, String group) {
return getNewBaseIntent(summaryNotificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, fcmJson.toString()).putExtra("summary", group);
private static Intent createBaseSummaryIntent(
int summaryNotificationId,
GenerateNotificationOpenIntent intentGenerator,
JSONObject fcmJson,
String group
) {
return intentGenerator.getNewBaseIntent(summaryNotificationId).putExtra(BUNDLE_KEY_ONESIGNAL_DATA, fcmJson.toString()).putExtra("summary", group);
}

private static void createSummaryIdDatabaseEntry(OneSignalDbHelper dbHelper, String group, int id) {
Expand Down Expand Up @@ -958,7 +1012,13 @@ static BigInteger getAccentColor(JSONObject fcmJson) {
return null;
}

private static void addNotificationActionButtons(JSONObject fcmJson, NotificationCompat.Builder mBuilder, int notificationId, String groupSummary) {
private static void addNotificationActionButtons(
JSONObject fcmJson,
GenerateNotificationOpenIntent intentGenerator,
NotificationCompat.Builder mBuilder,
int notificationId,
String groupSummary
) {
try {
JSONObject customJson = new JSONObject(fcmJson.optString("custom"));

Expand All @@ -975,7 +1035,7 @@ private static void addNotificationActionButtons(JSONObject fcmJson, Notificatio
JSONObject button = buttons.optJSONObject(i);
JSONObject bundle = new JSONObject(fcmJson.toString());

Intent buttonIntent = getNewBaseIntent(notificationId);
Intent buttonIntent = intentGenerator.getNewBaseIntent(notificationId);
buttonIntent.setAction("" + i); // Required to keep each action button from replacing extras of each other
buttonIntent.putExtra("action_button", true);
bundle.put(BUNDLE_KEY_ACTION_ID, button.optString("id"));
Expand All @@ -985,7 +1045,7 @@ private static void addNotificationActionButtons(JSONObject fcmJson, Notificatio
else if (fcmJson.has("grp"))
buttonIntent.putExtra("grp", fcmJson.optString("grp"));

PendingIntent buttonPIntent = getNewActionPendingIntent(notificationId, buttonIntent);
PendingIntent buttonPIntent = intentGenerator.getNewActionPendingIntent(notificationId, buttonIntent);

int buttonIcon = 0;
if (button.has("icon"))
Expand Down Expand Up @@ -1043,4 +1103,4 @@ private static int convertOSToAndroidPriority(int priority) {

return NotificationCompat.PRIORITY_MIN;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.onesignal

import android.app.PendingIntent
import android.content.Context
import android.content.Intent

class GenerateNotificationOpenIntent(
private val context: Context,
private val intent: Intent?,
private val startApp: Boolean
) {

private val notificationOpenedClass: Class<*> = NotificationOpenedReceiver::class.java

fun getNewBaseIntent(
notificationId: Int,
): Intent {
// We use SINGLE_TOP and CLEAR_TOP as we don't want more than one OneSignal invisible click
// tracking Activity instance around.
var intentFlags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP
if (!startApp) {
// If we don't want the app to launch we put OneSignal's invisible click tracking Activity on it's own task
// so it doesn't resume an existing one once it closes.
intentFlags =
intentFlags or (
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
)
}

return Intent(
context,
notificationOpenedClass
)
.putExtra(
GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID,
notificationId
)
.addFlags(intentFlags)
}

/**
* Creates a PendingIntent to attach to the notification click and it's action button(s).
* If the user interacts with the notification this normally starts the app or resumes it
* unless the app developer disables this via a OneSignal meta-data AndroidManifest.xml setting
*
* The default behavior is to open the app in the same way an Android homescreen launcher does.
* This means we expect the following behavior:
* 1. Starts the Activity defined in the app's AndroidManifest.xml as "android.intent.action.MAIN"
* 2. If the app is already running, instead the last activity will be resumed
* 3. If the app is not running (due to being push out of memory), the last activity will be resumed
* 4. If the app is no longer in the recent apps list, it is not resumed, same as #1 above.
* - App is removed from the recent app's list if it is swiped away or "clear all" is pressed.
*/
fun getNewActionPendingIntent(
requestCode: Int,
oneSignalIntent: Intent,
): PendingIntent? {
val launchIntent = getIntentVisible()
?:
// Even though the default app open action is disabled we still need to attach OneSignal's
// invisible Activity to capture click event to report click counts and etc.
// You may be thinking why not use a BroadcastReceiver instead of an invisible
// Activity? This could be done in a 5.0.0 release but can't be changed now as it is
// unknown if the app developer will be starting there own Activity from their
// OSNotificationOpenedHandler and that would have side-effects.
return PendingIntent.getActivity(
context,
requestCode,
oneSignalIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)


// This setups up a "Reverse Activity Trampoline"
// The first Activity to launch will be oneSignalIntent, which is an invisible
// Activity to track the click, fire OSNotificationOpenedHandler, etc. This Activity
// will finish quickly and the destination Activity, launchIntent, will be shown to the user
// since it is the next in the back stack.
return PendingIntent.getActivities(
context,
requestCode,
arrayOf(launchIntent, oneSignalIntent),
PendingIntent.FLAG_UPDATE_CURRENT
)
}

// Return the provide intent if one was set, otherwise default to opening the app.
private fun getIntentVisible(): Intent? {
if (intent != null) return intent
return getIntentAppOpen()
}

// Provides the default launcher Activity, if the app has one.
// - This is almost always true, one of the few exceptions being an app that is only a widget.
private fun getIntentAppOpen(): Intent? {
if (!startApp) return null

val launchIntent =
context.packageManager.getLaunchIntentForPackage(
context.packageName
)
?: return null

// Removing "package" from the intent treats the app as if it was started externally.
// - This is exactly what an Android Launcher does.
// This prevents another instance of the Activity from being created.
// Android 11 no longer requires nulling this out to get this behavior.
launchIntent.setPackage(null)
launchIntent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

return launchIntent
}
}
Loading