From 9c1805271876c7b71cd1f36103a88367b20d93ab Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Thu, 8 Jul 2021 21:57:53 -0700 Subject: [PATCH 01/17] Reverse Activity Trampolining Implementation * This commit contains the working implementation for Reverse Activity Trampolining Implementation. - Other considerations such as outcomes will be done in a follow up commit as part of this PR. * From a high level we are moving the logic of "should we open the app" to notification generation time instead of at click time. * The way this is being done is deciding if we want to display two Activities (OneSignal's invisible click tracking + app's launcher) or just OneSignal's Activity. * Comments added to the code in this commit go into more details. --- .../com/onesignal/GenerateNotification.java | 65 +++++++++++++++++-- .../main/java/com/onesignal/OneSignal.java | 2 +- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java index bc11f1e4ea..a2fc3ae800 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java @@ -127,23 +127,69 @@ 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 + * 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 */ - private static PendingIntent getNewActionPendingIntent(int requestCode, Intent intent) { - return PendingIntent.getActivity(currentContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + private static PendingIntent getNewActionPendingIntent(int requestCode, Intent oneSignalIntent) { + PendingIntent oneSignalActivityIntent = PendingIntent.getActivity(currentContext, requestCode, oneSignalIntent, PendingIntent.FLAG_UPDATE_CURRENT); + // 1. Check if the App developer disabled the default action of opening / resuming the app. + boolean defaultOpenActionDisabled = getDefaultAppOpenDisabled(); + if (defaultOpenActionDisabled) { + // 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 oneSignalActivityIntent; + } + + // 2. Check if the app defines a launcher Activity. This is almost always true, one of the few + // exceptions being an app that is only a widget. + Intent launchIntent = currentContext.getPackageManager().getLaunchIntentForPackage(currentContext.getPackageName()); + if (launchIntent == null) { + return oneSignalActivityIntent; + } + + // 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.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + // Launch desired Activity we want the user to be take to the followed by + // OneSignal's invisible notification open tracking Activity + // This allows OneSignal to track the click, fire OSNotificationOpenedHandler, etc while allowing + // the app developer to set the Activity they want at notification creation time. (FUTURE API FEATURE) + // AKA "Reverse Activity Trampolining" + Intent[] intents = { launchIntent, oneSignalIntent }; + return PendingIntent.getActivities(currentContext, requestCode, intents, PendingIntent.FLAG_UPDATE_CURRENT); } private static PendingIntent getNewDismissActionPendingIntent(int requestCode, Intent intent) { return PendingIntent.getBroadcast(currentContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); } + // An Intent that is used as a base for all notification open actions. Both notification clicks + // as well as notification action button clicks. private static Intent getNewBaseIntent(int notificationId) { + // We use SINGLE_TOP and CLEAR_TOP as we don't want more than one OneSignal invisible click + // tracking Activity instance around. + int intentFlags = Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP; + boolean defaultOpenActionDisabled = getDefaultAppOpenDisabled(); + if (defaultOpenActionDisabled) { + // 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 |= Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; + } + return new Intent(currentContext, notificationOpenedClass) .putExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notificationId) - .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + .addFlags(intentFlags); } private static Intent getNewBaseDismissIntent(int notificationId) { @@ -1043,4 +1089,9 @@ private static int convertOSToAndroidPriority(int priority) { return NotificationCompat.PRIORITY_MIN; } -} \ No newline at end of file + + private static boolean getDefaultAppOpenDisabled() { + return "DISABLE".equals(OSUtils.getManifestMeta(currentContext, "com.onesignal.NotificationOpened.DEFAULT")); + } + +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index 9bf020dcaa..843531482a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -2425,7 +2425,7 @@ private static boolean shouldInitDirectSessionFromNotificationOpen(Activity cont && !urlOpened && !defaultOpenActionDisabled && !inForeground - && startOrResumeApp(context); + // && startOrResumeApp(context); // TODO: Disabling in this commit only to test changes, in a follow up commit in this PR we will address this line } private static void notificationOpenedRESTCall(Context inContext, JSONArray dataArray) { From 5b851709398f8cc8b0f27b4722df282ec16f5885 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Fri, 23 Jul 2021 17:31:49 -0700 Subject: [PATCH 02/17] WIP - Improve generating intent logic --- .../sdktest/application/MainApplication.java | 2 +- .../onesignal/src/main/AndroidManifest.xml | 1 + .../com/onesignal/GenerateNotification.java | 8 ++ .../GenerateNotificationOpenIntent.kt | 121 ++++++++++++++++++ ...teNotificationOpenIntentFromPushPayload.kt | 51 ++++++++ .../OSNotificationOpenAppSettings.kt | 29 +++++ .../src/main/java/com/onesignal/OSUtils.java | 21 ++- .../main/java/com/onesignal/OneSignal.java | 41 ++---- 8 files changed, 235 insertions(+), 39 deletions(-) create mode 100644 OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt create mode 100644 OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt create mode 100644 OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java index 7c895aebf1..bd77833395 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java @@ -41,7 +41,7 @@ public void onCreate() { OSNotification notification = notificationReceivedEvent.getNotification(); JSONObject data = notification.getAdditionalData(); - notificationReceivedEvent.complete(null); + notificationReceivedEvent.complete(notification); }); OneSignal.unsubscribeWhenNotificationsAreDisabled(true); diff --git a/OneSignalSDK/onesignal/src/main/AndroidManifest.xml b/OneSignalSDK/onesignal/src/main/AndroidManifest.xml index ed445b01d1..8cdd87ecc0 100644 --- a/OneSignalSDK/onesignal/src/main/AndroidManifest.xml +++ b/OneSignalSDK/onesignal/src/main/AndroidManifest.xml @@ -135,6 +135,7 @@ diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java index a2fc3ae800..7c5b55892b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java @@ -131,6 +131,14 @@ private static CharSequence getTitle(JSONObject fcmJson) { * 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. */ private static PendingIntent getNewActionPendingIntent(int requestCode, Intent oneSignalIntent) { PendingIntent oneSignalActivityIntent = PendingIntent.getActivity(currentContext, requestCode, oneSignalIntent, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt new file mode 100644 index 0000000000..a1f7d05440 --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt @@ -0,0 +1,121 @@ +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? { + // OneSignal's invisible Activity to get the notification open event + val oneSignalActivityIntent = PendingIntent.getActivity( + context, + requestCode, + oneSignalIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + 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 oneSignalActivityIntent + + // Launch desired Activity we want the user to be take to the followed by + // OneSignal's invisible notification open tracking Activity + // This allows OneSignal to track the click, fire OSNotificationOpenedHandler, etc while allowing + // the app developer to set the Activity they want at notification creation time. (FUTURE API FEATURE) + // AKA "Reverse Activity Trampolining" + 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? { + // 1. Check if the App developer disabled the default action of opening / resuming the app. + if (!startApp) return null + + 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? { + 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 + } +} \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt new file mode 100644 index 0000000000..65a87daccf --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt @@ -0,0 +1,51 @@ +package com.onesignal + +import android.content.Context +import android.content.Intent +import android.net.Uri +import org.json.JSONObject + +/** + * Create a GenerateNotificationOpenIntent instance based on: + * * OSNotificationOpenAppSettings + * * Payload + */ +object GenerateNotificationOpenIntentFromPushPayload { + + fun create( + context: Context, + fcmPayload: JSONObject + ): GenerateNotificationOpenIntent { + + val payloadSpecificIntent = openBrowserIntent( + context, + fcmPayload + ) + + return GenerateNotificationOpenIntent( + context, + payloadSpecificIntent, + OSNotificationOpenAppSettings.getDefaultAppOpenDisabled(context) + ) + } + + private fun openBrowserIntent( + context: Context, + fcmPayload: JSONObject + ): Intent? { + if (OSNotificationOpenAppSettings.getSuppressLaunchURL(context)) return null + + val customJSON = JSONObject(fcmPayload.optString("custom")) + + if (customJSON.has("u")) { + val url = customJSON.optString("u") + if (url != "") { + val uri = Uri.parse(url.trim { it <= ' ' }) + return OSUtils.openURLInBrowserIntent(uri) + } + } + + return null + } + +} \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt new file mode 100644 index 0000000000..7e0de7e9cd --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt @@ -0,0 +1,29 @@ +package com.onesignal + +import android.content.Context + +/*** + * Settings that effect the OneSignal notification open behavior at the app level. + */ +object OSNotificationOpenAppSettings { + + /*** + * Should the default behavior of OneSignal be to always open / resume the app be disabled? + */ + fun getDefaultAppOpenDisabled(context: Context): Boolean { + return "DISABLE" == OSUtils.getManifestMeta( + context, + "com.onesignal.NotificationOpened.DEFAULT" + ) + } + + /*** + * Should the default behavior of OneSignal be to always open URLs be disabled? + */ + fun getSuppressLaunchURL(context: Context): Boolean { + return OSUtils.getManifestMetaBoolean( + context, + "com.onesignal.suppressLaunchURLs" + ) + } +} \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java index af7c7ade5d..0c7bf75adc 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java @@ -548,10 +548,16 @@ static void openURLInBrowser(@NonNull String url) { } private static void openURLInBrowser(@NonNull Uri uri) { + Intent intent = openURLInBrowserIntent(uri); + OneSignal.appContext.startActivity(intent); + } + + @NonNull + static Intent openURLInBrowserIntent(@NonNull Uri uri) { SchemaType type = uri.getScheme() != null ? SchemaType.fromString(uri.getScheme()) : null; if (type == null) { - type = SchemaType.HTTP; - if (!uri.toString().contains("://")) { + type = SchemaType.HTTP; + if (!uri.toString().contains("://")) { uri = Uri.parse("http://" + uri.toString()); } } @@ -568,11 +574,12 @@ private static void openURLInBrowser(@NonNull Uri uri) { break; } intent.addFlags( - Intent.FLAG_ACTIVITY_NO_HISTORY | - Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | - Intent.FLAG_ACTIVITY_MULTIPLE_TASK | - Intent.FLAG_ACTIVITY_NEW_TASK); - OneSignal.appContext.startActivity(intent); + Intent.FLAG_ACTIVITY_NO_HISTORY | + Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | + Intent.FLAG_ACTIVITY_MULTIPLE_TASK | + Intent.FLAG_ACTIVITY_NEW_TASK + ); + return intent; } // Creates a new Set that supports reads and writes from more than one thread at a time diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index 843531482a..dd1f7796a8 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -2180,6 +2180,11 @@ static void sendPurchases(JSONArray purchases, boolean newAsExisting, OneSignalR } } + // TODO: How to handle launch URL due to at notification generation changes. + // 1. (behavior change) Let app start then open browser + // 2. Move this code to GenerateNotifcation.java. + // 3. Detect URL is in notification and make it not start app automaticly. That why things work as before. + private static boolean openURLFromNotification(Context context, JSONArray dataArray) { //if applicable, check if the user provided privacy consent @@ -2367,6 +2372,8 @@ public void run() { trackFirebaseAnalytics.trackOpenedEvent(generateNotificationOpenedResult(data)); boolean urlOpened = false; + // TODO: Make this a helper method somewhere. + // TODO: Probably need to keep but consider if the reverse trampolining has any effect on this. boolean defaultOpenActionDisabled = "DISABLE".equals(OSUtils.getManifestMeta(context, "com.onesignal.NotificationOpened.DEFAULT")); if (!defaultOpenActionDisabled) @@ -2387,45 +2394,17 @@ static void applicationOpenedByNotification(@Nullable final String notificationI sessionManager.onDirectInfluenceFromNotificationOpen(appEntryState, notificationId); } - // 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. - static boolean startOrResumeApp(@NonNull Activity activity) { - Intent launchIntent = activity.getPackageManager().getLaunchIntentForPackage(activity.getPackageName()); - logger.debug("startOrResumeApp from context: " + activity + " isRoot: " + activity.isTaskRoot() + " with launchIntent: " + launchIntent); - - // Not all apps have a launcher intent, such as one that only provides a homescreen widget - if (launchIntent == null) - return false; - - // Removing package from the intent, this treats the app as if it was started externally. - // This gives us the resume app behavior noted above. - // Android 11 no longer requires nulling this out to get this behavior. - launchIntent.setPackage(null); - - activity.startActivity(launchIntent); - - return true; - } - /** * 1. App is not an alert * 2. Not a URL open - * 3. Manifest setting for com.onesignal.NotificationOpened.DEFAULT is not disabled - * 4. Manifest setting for com.onesignal.suppressLaunchURLs is not true - * 5. App is coming from the background - * 6. App open/resume intent exists + * 3. Manifest setting for com.onesignal.suppressLaunchURLs is not true + * 4. App is coming from the background */ private static boolean shouldInitDirectSessionFromNotificationOpen(Activity context, boolean fromAlert, boolean urlOpened, boolean defaultOpenActionDisabled) { return !fromAlert && !urlOpened && !defaultOpenActionDisabled - && !inForeground - // && startOrResumeApp(context); // TODO: Disabling in this commit only to test changes, in a follow up commit in this PR we will address this line + && !inForeground; } private static void notificationOpenedRESTCall(Context inContext, JSONArray dataArray) { From ce0c2efc64784220f10209b7630d554246405747 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Wed, 4 Aug 2021 18:26:49 -0700 Subject: [PATCH 03/17] Simplifed getDefaultAppOpenDisabled name * Shorter name and flips the logic to make it easier to read. --- .../GenerateNotificationOpenIntentFromPushPayload.kt | 2 +- .../main/java/com/onesignal/OSNotificationOpenAppSettings.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt index 65a87daccf..9e915c63b9 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt @@ -25,7 +25,7 @@ object GenerateNotificationOpenIntentFromPushPayload { return GenerateNotificationOpenIntent( context, payloadSpecificIntent, - OSNotificationOpenAppSettings.getDefaultAppOpenDisabled(context) + OSNotificationOpenAppSettings.getOpenApp(context) ) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt index 7e0de7e9cd..1401201c9a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt @@ -10,8 +10,8 @@ object OSNotificationOpenAppSettings { /*** * Should the default behavior of OneSignal be to always open / resume the app be disabled? */ - fun getDefaultAppOpenDisabled(context: Context): Boolean { - return "DISABLE" == OSUtils.getManifestMeta( + fun getOpenApp(context: Context): Boolean { + return "DISABLE" != OSUtils.getManifestMeta( context, "com.onesignal.NotificationOpened.DEFAULT" ) From 58d18f007b6f4994a5896e8ac994d7f31178c6ad Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Wed, 4 Aug 2021 18:32:09 -0700 Subject: [PATCH 04/17] Use GenerateNotificationOpenIntent * Putting the GenerateNotificationOpenIntent class added a previous commit to use. * Removed getNewBaseIntent & getNewActionPendingIntent from GenerateNotification.java as it was moved to GenerateNotificationOpenIntent --- .../com/onesignal/GenerateNotification.java | 185 +++++++++--------- 1 file changed, 93 insertions(+), 92 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java index 7c5b55892b..aa7ea676a8 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java @@ -127,79 +127,10 @@ private static CharSequence getTitle(JSONObject fcmJson) { return currentContext.getPackageManager().getApplicationLabel(currentContext.getApplicationInfo()); } - /** - * 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. - */ - private static PendingIntent getNewActionPendingIntent(int requestCode, Intent oneSignalIntent) { - PendingIntent oneSignalActivityIntent = PendingIntent.getActivity(currentContext, requestCode, oneSignalIntent, PendingIntent.FLAG_UPDATE_CURRENT); - // 1. Check if the App developer disabled the default action of opening / resuming the app. - boolean defaultOpenActionDisabled = getDefaultAppOpenDisabled(); - if (defaultOpenActionDisabled) { - // 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 oneSignalActivityIntent; - } - - // 2. Check if the app defines a launcher Activity. This is almost always true, one of the few - // exceptions being an app that is only a widget. - Intent launchIntent = currentContext.getPackageManager().getLaunchIntentForPackage(currentContext.getPackageName()); - if (launchIntent == null) { - return oneSignalActivityIntent; - } - - // 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.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - - // Launch desired Activity we want the user to be take to the followed by - // OneSignal's invisible notification open tracking Activity - // This allows OneSignal to track the click, fire OSNotificationOpenedHandler, etc while allowing - // the app developer to set the Activity they want at notification creation time. (FUTURE API FEATURE) - // AKA "Reverse Activity Trampolining" - Intent[] intents = { launchIntent, oneSignalIntent }; - return PendingIntent.getActivities(currentContext, requestCode, intents, PendingIntent.FLAG_UPDATE_CURRENT); - } - private static PendingIntent getNewDismissActionPendingIntent(int requestCode, Intent intent) { return PendingIntent.getBroadcast(currentContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - // An Intent that is used as a base for all notification open actions. Both notification clicks - // as well as notification action button clicks. - private static Intent getNewBaseIntent(int notificationId) { - // We use SINGLE_TOP and CLEAR_TOP as we don't want more than one OneSignal invisible click - // tracking Activity instance around. - int intentFlags = Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP; - boolean defaultOpenActionDisabled = getDefaultAppOpenDisabled(); - if (defaultOpenActionDisabled) { - // 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 |= Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; - } - - return new Intent(currentContext, notificationOpenedClass) - .putExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notificationId) - .addFlags(intentFlags); - } - private static Intent getNewBaseDismissIntent(int notificationId) { return new Intent(currentContext, notificationDismissedClass) .putExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notificationId) @@ -328,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 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 @@ -343,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); @@ -364,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 @@ -392,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); @@ -504,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); @@ -590,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 && @@ -676,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) @@ -700,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; @@ -710,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; @@ -750,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) { @@ -1012,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")); @@ -1029,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")); @@ -1039,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")) @@ -1097,9 +1103,4 @@ private static int convertOSToAndroidPriority(int priority) { return NotificationCompat.PRIORITY_MIN; } - - private static boolean getDefaultAppOpenDisabled() { - return "DISABLE".equals(OSUtils.getManifestMeta(currentContext, "com.onesignal.NotificationOpened.DEFAULT")); - } - } From b911ccdf252853dbdaa06ca431bc573858bfb1a0 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Wed, 4 Aug 2021 21:21:15 -0700 Subject: [PATCH 05/17] Formating fix up --- .../main/java/com/onesignal/GenerateNotificationOpenIntent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt index a1f7d05440..ac08f1727f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt @@ -114,7 +114,8 @@ class GenerateNotificationOpenIntent( // 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 + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED return launchIntent } From 1c4670b6db57838e505b4ef7b9476ece43254276 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Wed, 4 Aug 2021 21:22:18 -0700 Subject: [PATCH 06/17] rm open code that has been moved to intents --- .../main/java/com/onesignal/OneSignal.java | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index dd1f7796a8..f9cd195f3a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -2180,45 +2180,6 @@ static void sendPurchases(JSONArray purchases, boolean newAsExisting, OneSignalR } } - // TODO: How to handle launch URL due to at notification generation changes. - // 1. (behavior change) Let app start then open browser - // 2. Move this code to GenerateNotifcation.java. - // 3. Detect URL is in notification and make it not start app automaticly. That why things work as before. - - private static boolean openURLFromNotification(Context context, JSONArray dataArray) { - - //if applicable, check if the user provided privacy consent - if (shouldLogUserPrivacyConsentErrorMessageForMethodName(null)) - return false; - - int jsonArraySize = dataArray.length(); - - boolean urlOpened = false; - boolean launchUrlSuppress = OSUtils.getManifestMetaBoolean(context, "com.onesignal.suppressLaunchURLs"); - - for (int i = 0; i < jsonArraySize; i++) { - try { - JSONObject data = dataArray.getJSONObject(i); - if (!data.has("custom")) - continue; - - JSONObject customJSON = new JSONObject(data.optString("custom")); - - if (customJSON.has("u")) { - String url = customJSON.optString("u", null); - if (url != null && !launchUrlSuppress) { - OSUtils.openURLInBrowser(url); - urlOpened = true; - } - } - } catch (Throwable t) { - Log(LOG_LEVEL.ERROR, "Error parsing JSON item " + i + "/" + jsonArraySize + " for launching a web URL.", t); - } - } - - return urlOpened; - } - private static void runNotificationOpenedCallback(final JSONArray dataArray) { if (notificationOpenedHandler == null) { unprocessedOpenedNotifs.add(dataArray); @@ -2371,20 +2332,6 @@ public void run() { if (trackFirebaseAnalytics != null && getFirebaseAnalyticsEnabled()) trackFirebaseAnalytics.trackOpenedEvent(generateNotificationOpenedResult(data)); - boolean urlOpened = false; - // TODO: Make this a helper method somewhere. - // TODO: Probably need to keep but consider if the reverse trampolining has any effect on this. - boolean defaultOpenActionDisabled = "DISABLE".equals(OSUtils.getManifestMeta(context, "com.onesignal.NotificationOpened.DEFAULT")); - - if (!defaultOpenActionDisabled) - urlOpened = openURLFromNotification(context, data); - - logger.debug("handleNotificationOpen from context: " + context + " with fromAlert: " + fromAlert + " urlOpened: " + urlOpened + " defaultOpenActionDisabled: " + defaultOpenActionDisabled); - // Check if the notification click should lead to a DIRECT session - if (shouldInitDirectSessionFromNotificationOpen(context, fromAlert, urlOpened, defaultOpenActionDisabled)) { - applicationOpenedByNotification(notificationId); - } - runNotificationOpenedCallback(data); } @@ -2394,19 +2341,6 @@ static void applicationOpenedByNotification(@Nullable final String notificationI sessionManager.onDirectInfluenceFromNotificationOpen(appEntryState, notificationId); } - /** - * 1. App is not an alert - * 2. Not a URL open - * 3. Manifest setting for com.onesignal.suppressLaunchURLs is not true - * 4. App is coming from the background - */ - private static boolean shouldInitDirectSessionFromNotificationOpen(Activity context, boolean fromAlert, boolean urlOpened, boolean defaultOpenActionDisabled) { - return !fromAlert - && !urlOpened - && !defaultOpenActionDisabled - && !inForeground; - } - private static void notificationOpenedRESTCall(Context inContext, JSONArray dataArray) { for (int i = 0; i < dataArray.length(); i++) { try { From cb0158daac86b095e185cbcc919078c35ef2757d Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 9 Aug 2021 10:33:10 -0700 Subject: [PATCH 07/17] Account for IAM Preview with Activity trampolining --- ...enerateNotificationOpenIntentFromPushPayload.kt | 14 +++++++------- .../com/onesignal/NotificationOpenedProcessor.java | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt index 9e915c63b9..35e42bd54e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt @@ -16,17 +16,17 @@ object GenerateNotificationOpenIntentFromPushPayload { context: Context, fcmPayload: JSONObject ): GenerateNotificationOpenIntent { - - val payloadSpecificIntent = openBrowserIntent( + return GenerateNotificationOpenIntent( context, - fcmPayload + openBrowserIntent(context, fcmPayload), + shouldOpenApp(context, fcmPayload) ) + } - return GenerateNotificationOpenIntent( - context, - payloadSpecificIntent, + private fun shouldOpenApp(context: Context, fcmPayload: JSONObject): Boolean { + val isIAMPreviewNotification = NotificationBundleProcessor.inAppPreviewPushUUID(fcmPayload) != null + return isIAMPreviewNotification or OSNotificationOpenAppSettings.getOpenApp(context) - ) } private fun openBrowserIntent( diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java index 3f879ba8aa..f0d2bedf8f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java @@ -143,7 +143,6 @@ static boolean handleIAMPreviewOpen(@NonNull Activity context, @NonNull JSONObje if (previewUUID == null) return false; - OneSignal.startOrResumeApp(context); OneSignal.getInAppMessageController().displayPreviewMessage(previewUUID); return true; } From 5327beafd103eca5a6b1a4f463547516ea90faad Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 9 Aug 2021 16:08:12 -0700 Subject: [PATCH 08/17] Avoid creating a possiblely unused PendingIntent --- .../GenerateNotificationOpenIntent.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt index ac08f1727f..3dc0349a0e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt @@ -59,23 +59,20 @@ class GenerateNotificationOpenIntent( requestCode: Int, oneSignalIntent: Intent, ): PendingIntent? { - // OneSignal's invisible Activity to get the notification open event - val oneSignalActivityIntent = PendingIntent.getActivity( - context, - requestCode, - oneSignalIntent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - - val launchIntent = - getIntentVisible() ?: + 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 oneSignalActivityIntent + return PendingIntent.getActivity( + context, + requestCode, + oneSignalIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) // Launch desired Activity we want the user to be take to the followed by // OneSignal's invisible notification open tracking Activity From 5f7275069814b64220eb9697bb3c8ae4b1301505 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 9 Aug 2021 17:31:40 -0700 Subject: [PATCH 09/17] Moved notification intent tests * Moved these tests to GenerateNotificationRunner to match the source logic changes. --- .../onesignal/GenerateNotificationRunner.java | 70 ++++++++++ .../onesignal/MainOneSignalClassRunner.java | 121 ------------------ 2 files changed, 70 insertions(+), 121 deletions(-) diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java index 637ee38cbd..a639bbddce 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java @@ -30,6 +30,7 @@ import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; @@ -1129,6 +1130,64 @@ public void shouldSetButtonsCorrectly() throws Exception { assertEquals("id1", new JSONObject(json_data).optString(BUNDLE_KEY_ACTION_ID)); } + @Test + @Config(shadows = { ShadowGenerateNotification.class }) + public void shouldSetContentIntentForLaunchURL() throws Exception { + generateNotificationWithLaunchURL(); + + Intent[] intents = lastNotificationIntents(); + assertEquals(2, intents.length); + Intent intentLaunchURL = intents[0]; + assertEquals("android.intent.action.VIEW", intentLaunchURL.getAction()); + assertEquals("https://google.com", intentLaunchURL.getData().toString()); + + assertNotificationOpenedReceiver(intents[1]); + } + + @Test + @Config(shadows = { ShadowGenerateNotification.class }) + public void shouldNotSetContentIntentForLaunchURLIfDefaultNotificationOpenIsDisabled() throws Exception { + OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.NotificationOpened.DEFAULT", "DISABLE"); + generateNotificationWithLaunchURL(); + + Intent[] intents = lastNotificationIntents(); + assertEquals(1, intents.length); + assertNotificationOpenedReceiver(intents[0]); + } + + @Test + @Config(shadows = { ShadowGenerateNotification.class }) + public void shouldNotSetContentIntentForLaunchURLIfSuppress() throws Exception { + OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.suppressLaunchURLs", true); + generateNotificationWithLaunchURL(); + + Intent[] intents = lastNotificationIntents(); + assertEquals(2, intents.length); + assertOpenMainActivityIntent(intents[0]); + assertNotificationOpenedReceiver(intents[1]); + } + + private Intent[] lastNotificationIntents() { + PendingIntent pendingIntent = ShadowRoboNotificationManager.getLastNotif().contentIntent; + // NOTE: This is fragile until this robolectric issue is fixed: https://github.com/robolectric/robolectric/issues/6660 + return shadowOf(pendingIntent).getSavedIntents(); + } + + private void generateNotificationWithLaunchURL() throws Exception { + Bundle bundle = launchURLMockPayloadBundle(); + NotificationBundleProcessor_ProcessFromFCMIntentService(blankActivity, bundle); + threadAndTaskWait(); + } + + private void assertNotificationOpenedReceiver(@NonNull Intent intent) { + assertEquals("com.onesignal.NotificationOpenedReceiver", intent.getComponent().getClassName()); + } + + private void assertOpenMainActivityIntent(@NonNull Intent intent) { + assertEquals(Intent.ACTION_MAIN, intent.getAction()); + assertTrue(intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)); + } + @Test @Config(shadows = { ShadowGenerateNotification.class }) public void shouldSetAlertnessFieldsOnNormalPriority() { @@ -1211,6 +1270,17 @@ public void shouldSetExpireTimeCorrectlyWhenMissingFromPayload() throws Exceptio return bundle; } + @NonNull + private static Bundle launchURLMockPayloadBundle() throws JSONException { + Bundle bundle = new Bundle(); + bundle.putString("alert", "test"); + bundle.putString("custom", new JSONObject() {{ + put("i", "UUID"); + put("u", "https://google.com"); + }}.toString()); + return bundle; + } + @Test @Config(shadows = { ShadowOneSignalRestClient.class, ShadowOSWebView.class }) public void shouldShowInAppPreviewWhenInFocus() throws Exception { diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java index 81222a7d21..90c3e10a82 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java @@ -1042,127 +1042,6 @@ public void shouldNotFireNotificationOpenAgainAfterAppRestart() throws Exception assertEquals(null, lastNotificationOpenedBody); } - @Test - public void testOpeningLauncherActivity() throws Exception { - // First init run for appId to be saved - // At least OneSignal was init once for user to be subscribed - // If this doesn't' happen, notifications will not arrive - OneSignalInit(); - fastColdRestartApp(); - - AddLauncherIntentFilter(); - // From app launching normally - assertNotNull(shadowOf(blankActivity).getNextStartedActivity()); - // Will get appId saved - OneSignal.initWithContext(blankActivity.getApplicationContext()); - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - - assertNotNull(shadowOf(blankActivity).getNextStartedActivity()); - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - } - - @Test - public void testOpeningLaunchUrl() throws Exception { - // First init run for appId to be saved - // At least OneSignal was init once for user to be subscribed - // If this doesn't' happen, notifications will not arrive - OneSignalInit(); - fastColdRestartApp(); - OneSignal.initWithContext(blankActivity); - // Removes app launch - shadowOf(blankActivity).getNextStartedActivity(); - - // No OneSignal init here to test case where it is located in an Activity. - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\", \"u\": \"http://google.com\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - Intent intent = shadowOf(blankActivity).getNextStartedActivity(); - assertEquals("android.intent.action.VIEW", intent.getAction()); - assertEquals("http://google.com", intent.getData().toString()); - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - } - - @Test - public void testOpeningLaunchUrlWithDisableDefault() throws Exception { - // Add the 'com.onesignal.NotificationOpened.DEFAULT' as 'DISABLE' meta-data tag - OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.NotificationOpened.DEFAULT", "DISABLE"); - - // Removes app launch - shadowOf(blankActivity).getNextStartedActivity(); - - // No OneSignal init here to test case where it is located in an Activity. - - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\", \"u\": \"http://google.com\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - } - - @Test - public void testDisableOpeningLauncherActivityOnNotificationOpen() throws Exception { - // Add the 'com.onesignal.NotificationOpened.DEFAULT' as 'DISABLE' meta-data tag - OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.NotificationOpened.DEFAULT", "DISABLE"); - - // From app launching normally - assertNotNull(shadowOf(blankActivity).getNextStartedActivity()); - OneSignal.setAppId(ONESIGNAL_APP_ID); - OneSignal.initWithContext(blankActivity); - OneSignal.setNotificationOpenedHandler(getNotificationOpenedHandler()); - assertNull(lastNotificationOpenedBody); - - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - assertEquals("Test Msg", lastNotificationOpenedBody); - } - - @Test - public void testLaunchUrlSuppressTrue() throws Exception { - // Add the 'com.onesignal.suppressLaunchURLs' as 'true' meta-data tag - // First init run for appId to be saved - // At least OneSignal was init once for user to be subscribed - // If this doesn't' happen, notifications will not arrive - OneSignalInit(); - fastColdRestartApp(); - - // Add the 'com.onesignal.suppressLaunchURLs' as 'true' meta-data tag - OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.suppressLaunchURLs", true); - - // Removes app launch - shadowOf(blankActivity).getNextStartedActivity(); - - // Init with context since this is call before calling OneSignal_handleNotificationOpen internally - OneSignal.initWithContext(blankActivity); - - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\", \"u\": \"http://google.com\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - threadAndTaskWait(); - - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - } - - @Test - public void testLaunchUrlSuppressFalse() throws Exception { - // Add the 'com.onesignal.suppressLaunchURLs' as 'true' meta-data tag - // First init run for appId to be saved - // At least OneSignal was init once for user to be subscribed - // If this doesn't' happen, notifications will not arrive - OneSignalInit(); - fastColdRestartApp(); - - OneSignalShadowPackageManager.addManifestMetaData("com.onesignal.suppressLaunchURLs", false); - OneSignal.initWithContext(blankActivity); - - // Removes app launch - shadowOf(blankActivity).getNextStartedActivity(); - - // Init with context since this is call before calling OneSignal_handleNotificationOpen internally - OneSignal.initWithContext(blankActivity); - - OneSignal_handleNotificationOpen(blankActivity, new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\", \"u\": \"http://google.com\" } }]"), false, ONESIGNAL_NOTIFICATION_ID); - threadAndTaskWait(); - - Intent intent = shadowOf(blankActivity).getNextStartedActivity(); - assertEquals("android.intent.action.VIEW", intent.getAction()); - assertEquals("http://google.com", intent.getData().toString()); - assertNull(shadowOf(blankActivity).getNextStartedActivity()); - } - private static String notificationReceivedBody; private static int androidNotificationId; From 147dbbfaf10b69174fc28ea37bc9996a09d8a97e Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 9 Aug 2021 22:42:35 -0700 Subject: [PATCH 10/17] OSNotificationOpenBehaviorFromPushPayload * Split logic from GenerateNotificationOpenIntentFromPushPayload into OSNotificationOpenBehaviorFromPushPayload so this can be used independly in a follow up commit --- ...teNotificationOpenIntentFromPushPayload.kt | 35 +++++++------------ ...NotificationOpenBehaviorFromPushPayload.kt | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt index 35e42bd54e..c650149e52 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt @@ -7,45 +7,36 @@ import org.json.JSONObject /** * Create a GenerateNotificationOpenIntent instance based on: - * * OSNotificationOpenAppSettings + * * OSNotificationOpenBehaviorFromPushPayload * * Payload */ object GenerateNotificationOpenIntentFromPushPayload { - fun create( context: Context, fcmPayload: JSONObject ): GenerateNotificationOpenIntent { + val behavior = OSNotificationOpenBehaviorFromPushPayload( + context, + fcmPayload, + ) + return GenerateNotificationOpenIntent( context, - openBrowserIntent(context, fcmPayload), - shouldOpenApp(context, fcmPayload) + openBrowserIntent(behavior.uri), + shouldOpenApp(behavior.shouldOpenApp, fcmPayload) ) } - private fun shouldOpenApp(context: Context, fcmPayload: JSONObject): Boolean { + private fun shouldOpenApp(shouldOpenApp: Boolean, fcmPayload: JSONObject): Boolean { val isIAMPreviewNotification = NotificationBundleProcessor.inAppPreviewPushUUID(fcmPayload) != null return isIAMPreviewNotification or - OSNotificationOpenAppSettings.getOpenApp(context) + shouldOpenApp } private fun openBrowserIntent( - context: Context, - fcmPayload: JSONObject + uri: Uri?, ): Intent? { - if (OSNotificationOpenAppSettings.getSuppressLaunchURL(context)) return null - - val customJSON = JSONObject(fcmPayload.optString("custom")) - - if (customJSON.has("u")) { - val url = customJSON.optString("u") - if (url != "") { - val uri = Uri.parse(url.trim { it <= ' ' }) - return OSUtils.openURLInBrowserIntent(uri) - } - } - - return null + if (uri == null) return null + return OSUtils.openURLInBrowserIntent(uri) } - } \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt new file mode 100644 index 0000000000..8612869bf1 --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt @@ -0,0 +1,35 @@ +package com.onesignal + +import android.content.Context +import android.net.Uri +import org.json.JSONObject + +class OSNotificationOpenBehaviorFromPushPayload( + private val context: Context, + private val fcmPayload: JSONObject, +) { + + val shouldOpenApp: Boolean + get() { + if (!OSNotificationOpenAppSettings.getOpenApp(context)) return false + if (uri != null) return false + return true + } + + val uri: Uri? + get() { + if (OSNotificationOpenAppSettings.getSuppressLaunchURL(context)) return null + + val customJSON = JSONObject(fcmPayload.optString("custom")) + + if (customJSON.has("u")) { + val url = customJSON.optString("u") + if (url != "") { + return Uri.parse(url.trim { it <= ' ' }) + } + } + + return null + } + +} \ No newline at end of file From 3c46521d096c38221e3ef74e18ad41599a0d7048 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 9 Aug 2021 22:58:35 -0700 Subject: [PATCH 11/17] Added back outcomes logic * Added this logic back as some app developers may set them from the notification opened callback. - The fallback implemention of waiting for the app resume event doesn't catch this case. --- .../main/java/com/onesignal/OneSignal.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index f9cd195f3a..94ca9e1a1d 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -2332,9 +2332,30 @@ public void run() { if (trackFirebaseAnalytics != null && getFirebaseAnalyticsEnabled()) trackFirebaseAnalytics.trackOpenedEvent(generateNotificationOpenedResult(data)); + if (shouldInitDirectSessionFromNotificationOpen(context, data)) { + applicationOpenedByNotification(notificationId); + } + runNotificationOpenedCallback(data); } + private static boolean shouldInitDirectSessionFromNotificationOpen(Activity context, final JSONArray data) { + if (inForeground) { + return false; + } + + try { + JSONObject interactedNotificationData = data.getJSONObject(0); + return new OSNotificationOpenBehaviorFromPushPayload( + context, + interactedNotificationData + ).getShouldOpenApp(); + } catch (JSONException e) { + e.printStackTrace(); + } + return true; + } + static void applicationOpenedByNotification(@Nullable final String notificationId) { // We want to set the app entry state to NOTIFICATION_CLICK when coming from background appEntryState = AppEntryAction.NOTIFICATION_CLICK; From 17640634da68f1329c08c7ab191a4a900039cb04 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Tue, 10 Aug 2021 09:40:00 -0700 Subject: [PATCH 12/17] rm HMS test that no longer applies * This logic was moved tot the notification generation as is already covered by tests there. --- ...ificationOpenedActivityHMSIntegrationTestsRunner.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java index 0263c49dbb..6f2941e0da 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java @@ -154,15 +154,6 @@ public void emptyIntent_doesNotThrow() { helper_startHMSOpenActivity(helper_baseHMSOpenIntent()); } - @Test - public void barebonesOSPayload_startsMainActivity() throws Exception { - helper_initSDKAndFireHMSNotificationBarebonesOSOpenIntent(); - - Intent startedActivity = shadowOf((Application) ApplicationProvider.getApplicationContext()).getNextStartedActivity(); - assertNotNull(startedActivity); - assertEquals(startedActivity.getComponent().getClassName(), BlankActivity.class.getName()); - } - @Test public void barebonesOSPayload_makesNotificationOpenRequest() throws Exception { helper_initSDKAndFireHMSNotificationBarebonesOSOpenIntent(); From bba36bed55b8e8551d2190beb9e4eb3df272b77b Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Tue, 10 Aug 2021 10:19:30 -0700 Subject: [PATCH 13/17] mv getOpenApp -> getShouldOpenActivity * Renamed to getShouldOpenActivity as getOpenApp was incorrectly describing the behavior * Fixed usage of startApp * Added getShouldOpenActivity usage to uri --- .../java/com/onesignal/GenerateNotificationOpenIntent.kt | 5 ++--- .../main/java/com/onesignal/OSNotificationOpenAppSettings.kt | 5 +++-- .../onesignal/OSNotificationOpenBehaviorFromPushPayload.kt | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt index 3dc0349a0e..364d928dda 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt @@ -89,9 +89,6 @@ class GenerateNotificationOpenIntent( // Return the provide intent if one was set, otherwise default to opening the app. private fun getIntentVisible(): Intent? { - // 1. Check if the App developer disabled the default action of opening / resuming the app. - if (!startApp) return null - if (intent != null) return intent return getIntentAppOpen() } @@ -99,6 +96,8 @@ class GenerateNotificationOpenIntent( // 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 diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt index 1401201c9a..a6bfa8792e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenAppSettings.kt @@ -8,9 +8,10 @@ import android.content.Context object OSNotificationOpenAppSettings { /*** - * Should the default behavior of OneSignal be to always open / resume the app be disabled? + * When the notification is tapped on should it show an Activity? + * This could be resuming / opening the app or opening the URL on the notification. */ - fun getOpenApp(context: Context): Boolean { + fun getShouldOpenActivity(context: Context): Boolean { return "DISABLE" != OSUtils.getManifestMeta( context, "com.onesignal.NotificationOpened.DEFAULT" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt index 8612869bf1..b117fba07e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt @@ -11,13 +11,14 @@ class OSNotificationOpenBehaviorFromPushPayload( val shouldOpenApp: Boolean get() { - if (!OSNotificationOpenAppSettings.getOpenApp(context)) return false + if (!OSNotificationOpenAppSettings.getShouldOpenActivity(context)) return false if (uri != null) return false return true } val uri: Uri? get() { + if (!OSNotificationOpenAppSettings.getShouldOpenActivity(context)) return null if (OSNotificationOpenAppSettings.getSuppressLaunchURL(context)) return null val customJSON = JSONObject(fcmPayload.optString("custom")) From a3dd9f5fcf1b7c49a62f6f96385d165e43c6cfb5 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Tue, 10 Aug 2021 14:43:45 -0700 Subject: [PATCH 14/17] Updated inAppPreviewPushUUID after rebase --- .../onesignal/GenerateNotificationOpenIntentFromPushPayload.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt index c650149e52..54209f9c18 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntentFromPushPayload.kt @@ -28,7 +28,7 @@ object GenerateNotificationOpenIntentFromPushPayload { } private fun shouldOpenApp(shouldOpenApp: Boolean, fcmPayload: JSONObject): Boolean { - val isIAMPreviewNotification = NotificationBundleProcessor.inAppPreviewPushUUID(fcmPayload) != null + val isIAMPreviewNotification = OSInAppMessagePreviewHandler.inAppPreviewPushUUID(fcmPayload) != null return isIAMPreviewNotification or shouldOpenApp } From 238e60c38fef878c59d18ddc8444d82225c96f37 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Tue, 10 Aug 2021 15:29:37 -0700 Subject: [PATCH 15/17] Fix summary notification update logic * FIxed issue where summary notification did not update with all data from the payload when a child notification is removed. --- .../onesignal/NotificationSummaryManager.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java index 5ac83bce9d..fa19caea75 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java @@ -43,12 +43,15 @@ static void updateSummaryNotificationAfterChildRemoved(Context context, OneSigna cursor.close(); } } - + private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context context, OneSignalDb db, String group, boolean dismissed) { Cursor cursor = db.query( NotificationTable.TABLE_NAME, - new String[] { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, // return columns - NotificationTable.COLUMN_NAME_CREATED_TIME }, + new String[] { + NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, + NotificationTable.COLUMN_NAME_CREATED_TIME, + NotificationTable.COLUMN_NAME_FULL_DATA, + }, NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + // Where String NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + @@ -102,22 +105,22 @@ private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context try { cursor.moveToFirst(); Long datetime = cursor.getLong(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_CREATED_TIME)); + String jsonStr = cursor.getString(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_FULL_DATA)); cursor.close(); - + Integer androidNotifId = getSummaryNotificationId(db, group); if (androidNotifId == null) return cursor; - + OSNotificationGenerationJob notificationJob = new OSNotificationGenerationJob(context); notificationJob.setRestoring(true); notificationJob.setShownTimeStamp(datetime); - - JSONObject payload = new JSONObject(); - payload.put("grp", group); - notificationJob.setJsonPayload(payload); - + notificationJob.setJsonPayload(new JSONObject(jsonStr)); + GenerateNotification.updateSummaryNotification(notificationJob); - } catch (JSONException e) {} + } catch (JSONException e) { + e.printStackTrace(); + } return cursor; } From fe3929184483b993362bc675bd805fc85c818cf8 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Thu, 12 Aug 2021 12:02:48 -0700 Subject: [PATCH 16/17] fixup! OSNotificationOpenBehaviorFromPushPayload --- .../onesignal/OSNotificationOpenBehaviorFromPushPayload.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt index b117fba07e..193405cfd1 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationOpenBehaviorFromPushPayload.kt @@ -11,9 +11,8 @@ class OSNotificationOpenBehaviorFromPushPayload( val shouldOpenApp: Boolean get() { - if (!OSNotificationOpenAppSettings.getShouldOpenActivity(context)) return false - if (uri != null) return false - return true + return OSNotificationOpenAppSettings.getShouldOpenActivity(context) + && uri == null } val uri: Uri? From 07462cc053bd129674f243ba84fddfdc5d56b815 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Thu, 12 Aug 2021 12:04:46 -0700 Subject: [PATCH 17/17] fixup! WIP - Improve generating intent logic --- .../com/onesignal/GenerateNotificationOpenIntent.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt index 364d928dda..caf6e7b71a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotificationOpenIntent.kt @@ -74,11 +74,12 @@ class GenerateNotificationOpenIntent( PendingIntent.FLAG_UPDATE_CURRENT ) - // Launch desired Activity we want the user to be take to the followed by - // OneSignal's invisible notification open tracking Activity - // This allows OneSignal to track the click, fire OSNotificationOpenedHandler, etc while allowing - // the app developer to set the Activity they want at notification creation time. (FUTURE API FEATURE) - // AKA "Reverse Activity Trampolining" + + // 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, @@ -115,4 +116,4 @@ class GenerateNotificationOpenIntent( return launchIntent } -} \ No newline at end of file +}