Skip to content
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

[Change] Notification Activity Trampoline forward instead of reverse #1581

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 @@ -275,9 +275,8 @@ private static boolean showNotification(OSNotificationGenerationJob notification
JSONObject fcmJson = notificationJob.getJsonPayload();
String group = fcmJson.optString("grp", null);

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

ArrayList<StatusBarNotification> grouplessNotifs = new ArrayList<>();
Expand Down Expand Up @@ -368,7 +367,7 @@ private static boolean showNotification(OSNotificationGenerationJob notification

private static Notification createGenericPendingIntentsForNotif(
NotificationCompat.Builder notifBuilder,
GenerateNotificationOpenIntent intentGenerator,
IntentGeneratorForAttachingToNotifications intentGenerator,
JSONObject gcmBundle,
int notificationId
) {
Expand All @@ -385,7 +384,7 @@ private static Notification createGenericPendingIntentsForNotif(

private static void createGenericPendingIntentsForGroup(
NotificationCompat.Builder notifBuilder,
GenerateNotificationOpenIntent intentGenerator,
IntentGeneratorForAttachingToNotifications intentGenerator,
JSONObject gcmBundle,
String group,
int notificationId
Expand Down Expand Up @@ -495,9 +494,8 @@ 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
IntentGeneratorForAttachingToNotifications intentGenerator = new IntentGeneratorForAttachingToNotifications(
currentContext
);

String group = fcmJson.optString("grp", null);
Expand Down Expand Up @@ -706,7 +704,7 @@ private static void createSummaryNotification(OSNotificationGenerationJob notifi
@RequiresApi(api = Build.VERSION_CODES.M)
private static void createGrouplessSummaryNotification(
OSNotificationGenerationJob notificationJob,
GenerateNotificationOpenIntent intentGenerator,
IntentGeneratorForAttachingToNotifications intentGenerator,
int grouplessNotifCount
) {
JSONObject fcmJson = notificationJob.getJsonPayload();
Expand Down Expand Up @@ -763,7 +761,7 @@ private static void createGrouplessSummaryNotification(

private static Intent createBaseSummaryIntent(
int summaryNotificationId,
GenerateNotificationOpenIntent intentGenerator,
IntentGeneratorForAttachingToNotifications intentGenerator,
JSONObject fcmJson,
String group
) {
Expand Down Expand Up @@ -1034,7 +1032,7 @@ static BigInteger getAccentColor(JSONObject fcmJson) {

private static void addNotificationActionButtons(
JSONObject fcmJson,
GenerateNotificationOpenIntent intentGenerator,
IntentGeneratorForAttachingToNotifications intentGenerator,
NotificationCompat.Builder mBuilder,
int notificationId,
String groupSummary
Expand Down
Original file line number Diff line number Diff line change
@@ -1,128 +1,31 @@
package com.onesignal

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.RequiresApi

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

private val notificationOpenedClassAndroid23Plus: Class<*> = NotificationOpenedReceiver::class.java
private val notificationOpenedClassAndroid22AndOlder: Class<*> = NotificationOpenedReceiverAndroid22AndOlder::class.java

fun getNewBaseIntent(
notificationId: Int,
): Intent {
val intent =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M)
getNewBaseIntentAndroidAPI23Plus()
else
getNewBaseIntentAndroidAPI22AndOlder()

return intent
.putExtra(
GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID,
notificationId
)
// We use SINGLE_TOP and CLEAR_TOP as we don't want more than one OneSignal invisible click
// tracking Activity instance around.
.addFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP
)
}

@RequiresApi(android.os.Build.VERSION_CODES.M)
private fun getNewBaseIntentAndroidAPI23Plus(): Intent {
return Intent(
context,
notificationOpenedClassAndroid23Plus
)
}

// See NotificationOpenedReceiverAndroid22AndOlder.kt for details
@Deprecated("Use getNewBaseIntentAndroidAPI23Plus instead for Android 6+")
private fun getNewBaseIntentAndroidAPI22AndOlder(): Intent {
val intent = Intent(
context,
notificationOpenedClassAndroid22AndOlder
)

if (getIntentVisible() == null) {
// If we don't show a visible Activity put OneSignal's invisible click tracking
// Activity on it's own task so it doesn't resume an existing one once it closes.
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
)
}

return intent
fun getIntentVisible(): Intent? {
if (intent != null) return intent
return getIntentAppOpen()
}

/**
* 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 opens 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.
* - 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 flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
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,
flags
)


// 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),
flags
)
}

// 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

// Is null for apps that only provide a widget for it's UI.
val launchIntent =
context.packageManager.getLaunchIntentForPackage(
context.packageName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.onesignal

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.RequiresApi

class IntentGeneratorForAttachingToNotifications(
val context: Context
) {
private val notificationOpenedClassAndroid23Plus: Class<*> = NotificationOpenedReceiver::class.java
private val notificationOpenedClassAndroid22AndOlder: Class<*> = NotificationOpenedReceiverAndroid22AndOlder::class.java

fun getNewBaseIntent(
notificationId: Int,
): Intent {
val intent =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M)
getNewBaseIntentAndroidAPI23Plus()
else
getNewBaseIntentAndroidAPI22AndOlder()

return intent
.putExtra(
GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID,
notificationId
)
// We use SINGLE_TOP and CLEAR_TOP as we don't want more than one OneSignal invisible click
// tracking Activity instance around.
.addFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP
)
}

@RequiresApi(android.os.Build.VERSION_CODES.M)
private fun getNewBaseIntentAndroidAPI23Plus(): Intent {
return Intent(
context,
notificationOpenedClassAndroid23Plus
)
}

// See NotificationOpenedReceiverAndroid22AndOlder.kt for details
@Deprecated("Use getNewBaseIntentAndroidAPI23Plus instead for Android 6+")
private fun getNewBaseIntentAndroidAPI22AndOlder(): Intent {
val intent = Intent(
context,
notificationOpenedClassAndroid22AndOlder
)
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
)
return intent
}

fun getNewActionPendingIntent(
requestCode: Int,
oneSignalIntent: Intent,
): PendingIntent? {
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
return PendingIntent.getActivity(
context,
requestCode,
oneSignalIntent,
flags
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ static void processBundleFromReceiver(Context context, final Bundle bundle, fina
bundleResult.setOneSignalPayload(true);
maximizeButtonsFromBundle(bundle);

if (OSInAppMessagePreviewHandler.inAppMessagePreviewHandled(context, bundle)) {
if (OSInAppMessagePreviewHandler.notificationReceived(context, bundle)) {
// Return early, we don't want the extender service or etc. to fire for IAM previews
bundleResult.setInAppPreviewShown(true);
bundleReceiverCallback.onBundleProcessed(bundleResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ static void processIntent(Context context, Intent intent) {
if (!(context instanceof Activity))
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "NotificationOpenedProcessor processIntent from an non Activity context: " + context);
else OneSignal.handleNotificationOpen((Activity) context, intentExtras.getDataArray(),
false, OSNotificationFormatHelper.getOSNotificationIdFromJson(intentExtras.getJsonData()));
OSNotificationFormatHelper.getOSNotificationIdFromJson(intentExtras.getJsonData()));
}
}

Expand All @@ -126,7 +126,7 @@ static OSNotificationIntentExtras processToOpenIntent(Context context, Intent in

if (!(context instanceof Activity))
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "NotificationOpenedProcessor processIntent from an non Activity context: " + context);
else if (handleIAMPreviewOpen((Activity) context, jsonData))
else if (OSInAppMessagePreviewHandler.notificationOpened((Activity) context, jsonData))
return null;

jsonData.put(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, intent.getIntExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, 0));
Expand All @@ -143,15 +143,6 @@ else if (handleIAMPreviewOpen((Activity) context, jsonData))
return new OSNotificationIntentExtras(dataArray, jsonData);
}

static boolean handleIAMPreviewOpen(@NonNull Activity context, @NonNull JSONObject jsonData) {
String previewUUID = OSInAppMessagePreviewHandler.inAppPreviewPushUUID(jsonData);
if (previewUUID == null)
return false;

OneSignal.getInAppMessageController().displayPreviewMessage(previewUUID);
return true;
}

private static void addChildNotifications(JSONArray dataArray, String summaryGroup, OneSignalDbHelper writableDb) {
String[] retColumn = { NotificationTable.COLUMN_NAME_FULL_DATA };
String[] whereArgs = { summaryGroup };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ private static void reformatButtonClickAction(@NonNull JSONObject jsonData) {
}

private static void handleProcessJsonOpenData(@NonNull Activity activity, @NonNull JSONObject jsonData) {
if (NotificationOpenedProcessor.handleIAMPreviewOpen(activity, jsonData))
if (OSInAppMessagePreviewHandler.notificationOpened(activity, jsonData))
return;

OneSignal.handleNotificationOpen(
activity,
new JSONArray().put(jsonData),
true,
OSNotificationFormatHelper.getOSNotificationIdFromJson(jsonData)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.onesignal

import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.annotation.ChecksSdkIntAtLeast
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

internal object OSInAppMessagePreviewHandler {
@JvmStatic
fun inAppMessagePreviewHandled(context: Context?, bundle: Bundle?): Boolean {
fun notificationReceived(context: Context?, bundle: Bundle?): Boolean {
val pushPayloadJson = NotificationBundleProcessor.bundleAsJSONObject(bundle)
// Show In-App message preview it is in the payload & the app is in focus
val previewUUID = inAppPreviewPushUUID(pushPayloadJson) ?: return false
Expand All @@ -23,6 +26,15 @@ internal object OSInAppMessagePreviewHandler {
return true
}

@JvmStatic
fun notificationOpened(activity: Activity, jsonData: JSONObject): Boolean {
val previewUUID = inAppPreviewPushUUID(jsonData) ?: return false

OneSignal.openDestinationActivity(activity, JSONArray().put(jsonData))
OneSignal.getInAppMessageController().displayPreviewMessage(previewUUID)
return true
}

@JvmStatic
fun inAppPreviewPushUUID(payload: JSONObject): String? {
val osCustom: JSONObject = try {
Expand All @@ -43,5 +55,6 @@ internal object OSInAppMessagePreviewHandler {
}

// Validate that the current Android device is Android 4.4 or higher
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.KITKAT)
private fun shouldDisplayNotification(): Boolean = Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
}
}
Loading