Skip to content
Open
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
12 changes: 6 additions & 6 deletions OneSignalSDK/detekt/detekt-baseline-notifications.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<CurrentIssues>
<ID>ComplexCondition:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$updateSummary &amp;&amp; summaryList.size > 1 || !updateSummary &amp;&amp; summaryList.size > 0</ID>
<ID>ComplexMethod:NotificationChannelManager.kt$NotificationChannelManager$@RequiresApi(api = Build.VERSION_CODES.O) @Throws( JSONException::class, ) private fun createChannel( context: Context, notificationManager: NotificationManager, payload: JSONObject, ): String</ID>
<ID>ComplexMethod:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, isRestoring: Boolean, timestamp: Long, )</ID>
<ID>ComplexMethod:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, restoreReason: NotificationRestoreReason?, timestamp: Long, )</ID>
<ID>ComplexMethod:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$override suspend fun createSummaryNotification( notificationJob: NotificationGenerationJob, notifBuilder: NotificationDisplayBuilder.OneSignalNotificationBuilder?, groupAlertBehavior: Int, )</ID>
<ID>ConstructorParameterNaming:BadgeCountUpdater.kt$BadgeCountUpdater$private val _applicationService: IApplicationService</ID>
<ID>ConstructorParameterNaming:BadgeCountUpdater.kt$BadgeCountUpdater$private val _databaseProvider: IDatabaseProvider</ID>
Expand Down Expand Up @@ -124,7 +124,7 @@
<ID>LongMethod:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$override fun getBaseOneSignalNotificationBuilder(notificationJob: NotificationGenerationJob): OneSignalNotificationBuilder</ID>
<ID>LongMethod:NotificationDisplayer.kt$NotificationDisplayer$@Throws(Throwable::class) private fun addBackgroundImage( fcmJson: JSONObject, notifBuilder: NotificationCompat.Builder?, )</ID>
<ID>LongMethod:NotificationDisplayer.kt$NotificationDisplayer$private suspend fun showNotification(notificationJob: NotificationGenerationJob): Boolean</ID>
<ID>LongMethod:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, isRestoring: Boolean, timestamp: Long, )</ID>
<ID>LongMethod:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, restoreReason: NotificationRestoreReason?, timestamp: Long, )</ID>
<ID>LongMethod:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$private suspend fun saveNotification( notificationJob: NotificationGenerationJob, opened: Boolean, )</ID>
<ID>LongMethod:NotificationLifecycleService.kt$NotificationLifecycleService$override suspend fun notificationOpened( activity: Activity, data: JSONArray, )</ID>
<ID>LongMethod:NotificationRepository.kt$NotificationRepository$override suspend fun createNotification( id: String, groupId: String?, collapseKey: String?, shouldDismissIdenticals: Boolean, isOpened: Boolean, androidId: Int, title: String?, body: String?, expireTime: Long, jsonPayload: String, )</ID>
Expand All @@ -134,7 +134,7 @@
<ID>LongMethod:NotificationsModule.kt$NotificationsModule$override fun register(builder: ServiceBuilder)</ID>
<ID>LongMethod:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$@RequiresApi(api = Build.VERSION_CODES.M) override suspend fun createGrouplessSummaryNotification( notificationJob: NotificationGenerationJob, intentGenerator: IntentGeneratorForAttachingToNotifications, grouplessNotifCount: Int, groupAlertBehavior: Int, )</ID>
<ID>LongMethod:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$override suspend fun createSummaryNotification( notificationJob: NotificationGenerationJob, notifBuilder: NotificationDisplayBuilder.OneSignalNotificationBuilder?, groupAlertBehavior: Int, )</ID>
<ID>LongParameterList:INotificationGenerationWorkManager.kt$INotificationGenerationWorkManager$( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, isRestoring: Boolean, isHighPriority: Boolean, )</ID>
<ID>LongParameterList:INotificationGenerationWorkManager.kt$INotificationGenerationWorkManager$( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, restoreReason: NotificationRestoreReason?, isHighPriority: Boolean, )</ID>
<ID>LongParameterList:INotificationRepository.kt$INotificationRepository$( id: String, groupId: String?, collapseKey: String?, shouldDismissIdenticals: Boolean, isOpened: Boolean, androidId: Int, title: String?, body: String?, expireTime: Long, jsonPayload: String, )</ID>
<ID>LongParameterList:NotificationLifecycleService.kt$NotificationLifecycleService$( private val _applicationService: IApplicationService, private val _time: ITime, private val _configModelStore: ConfigModelStore, private val _influenceManager: IInfluenceManager, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _backend: INotificationBackendService, private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager, private val _analyticsTracker: IAnalyticsTracker, )</ID>
<ID>LoopWithTooManyJumpStatements:NotificationLifecycleService.kt$NotificationLifecycleService$for (i in 0 until data.length()) { val notificationId = NotificationFormatHelper.getOSNotificationIdFromJson(data[i] as JSONObject?) ?: continue if (postedOpenedNotifIds.contains(notificationId)) { continue } postedOpenedNotifIds.add(notificationId) suspendifyWithErrorHandling( useIO = true, // or false for CPU operations block = { confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType) }, onError = { ex -> if (ex is BackendException) { Logging.info("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.info("Unexpected error in notification opened confirmation", ex) } }, ) }</ID>
Expand Down Expand Up @@ -203,10 +203,10 @@
<ID>ReturnCount:NotificationDisplayer.kt$NotificationDisplayer$private fun getBitmapFromAssetsOrResourceName(bitmapStr: String): Bitmap?</ID>
<ID>ReturnCount:NotificationDisplayer.kt$NotificationDisplayer$private fun getResourceIcon(iconName: String?): Int</ID>
<ID>ReturnCount:NotificationFormatHelper.kt$NotificationFormatHelper$private fun getOSNotificationIdFromBundle(bundle: Bundle?): String?</ID>
<ID>ReturnCount:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, isRestoring: Boolean, timestamp: Long, )</ID>
<ID>ReturnCount:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$override suspend fun processNotificationData( context: Context, androidNotificationId: Int, jsonPayload: JSONObject, restoreReason: NotificationRestoreReason?, timestamp: Long, )</ID>
<ID>ReturnCount:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$private fun shouldFireForegroundHandlers(notificationJob: NotificationGenerationJob): Boolean</ID>
<ID>ReturnCount:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$private suspend fun processHandlerResponse( notificationJob: NotificationGenerationJob, wantsToDisplay: Boolean, isRestoring: Boolean, ): Boolean?</ID>
<ID>ReturnCount:NotificationGenerationWorkManager.kt$NotificationGenerationWorkManager$override fun beginEnqueueingWork( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, isRestoring: Boolean, isHighPriority: Boolean, ): Boolean</ID>
<ID>ReturnCount:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$private suspend fun processHandlerResponse( notificationJob: NotificationGenerationJob, wantsToDisplay: Boolean, restoreReason: NotificationRestoreReason?, ): Boolean?</ID>
<ID>ReturnCount:NotificationGenerationWorkManager.kt$NotificationGenerationWorkManager$override fun beginEnqueueingWork( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, restoreReason: NotificationRestoreReason?, isHighPriority: Boolean, ): Boolean</ID>
<ID>ReturnCount:NotificationGenerationWorkManager.kt$NotificationGenerationWorkManager.NotificationGenerationWorker$override suspend fun doWork(): Result</ID>
<ID>ReturnCount:NotificationHelper.kt$NotificationHelper$fun areNotificationsEnabled( context: Context, channelId: String? = null, ): Boolean</ID>
<ID>ReturnCount:NotificationHelper.kt$NotificationHelper$fun getCampaignNameFromNotification(notification: INotification): String</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ interface INotificationReceivedEvent {
*/
val notification: IDisplayableMutableNotification

/**
* True when OneSignal is showing this notification again after your app already received it.
* Happens after a reboot or app update, and when a group collapses to one notification.
*
* Skip one-time work like analytics. Still call `notification.setExtender(...)` so a rebuilt
* notification keeps your customizations.
* Call `preventDefault(true)` to stop a shade restore from coming back. A group-collapse
* rebuild is left in place. The no-argument [preventDefault] waits up to 30 seconds for
* `notification.display()`.
*/
val restoring: Boolean
get() = false

/**
* Call this to prevent OneSignal from displaying the notification automatically. The notification
* can still be manually displayed using `notification.display()`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.onesignal.notifications.INotificationReceivedEvent
internal class NotificationReceivedEvent(
override val context: Context,
override val notification: Notification,
override val restoring: Boolean,
) : INotificationReceivedEvent {
var isPreventDefault: Boolean = false
var discard: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.onesignal.notifications.internal.bundle.INotificationBundleProcessor
import com.onesignal.notifications.internal.common.NotificationConstants
import com.onesignal.notifications.internal.common.NotificationFormatHelper
import com.onesignal.notifications.internal.common.NotificationPriorityMapper
import com.onesignal.notifications.internal.common.NotificationRestoreReason
import com.onesignal.notifications.internal.generation.INotificationGenerationWorkManager
import org.json.JSONArray
import org.json.JSONException
Expand Down Expand Up @@ -84,7 +85,8 @@ internal class NotificationBundleProcessor(

val jsonPayload = JSONUtils.bundleAsJSONObject(bundle)
val timestamp = _time.currentTimeMillis / 1000L
val isRestoring = bundle.getBoolean("is_restoring", false)
val restoreReason =
if (bundle.getBoolean("is_restoring", false)) NotificationRestoreReason.SHADE_RESTORE else null
val isHighPriority = NotificationPriorityMapper.isHighPriority(bundle.getString("pri", "0").toInt())

val osNotificationId = NotificationFormatHelper.getOSNotificationIdFromJson(jsonPayload)
Expand All @@ -103,7 +105,7 @@ internal class NotificationBundleProcessor(
androidNotificationId,
jsonPayload,
timestamp,
isRestoring,
restoreReason,
isHighPriority,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class NotificationGenerationJob(
var overriddenTitleFromExtender: CharSequence? = null
var overriddenSound: Uri? = null
var overriddenFlags: Int? = null
var orgFlags: Int? = null

// The payload's sound, saved before the extender runs so we can tell if the extender changed it.
var orgSound: Uri? = null

constructor(jsonPayload: JSONObject, time: ITime) : this(
Expand Down Expand Up @@ -73,7 +74,6 @@ class NotificationGenerationJob(
", overriddenTitleFromExtender=" + overriddenTitleFromExtender +
", overriddenSound=" + overriddenSound +
", overriddenFlags=" + overriddenFlags +
", orgFlags=" + orgFlags +
", orgSound=" + orgSound +
", notification=" + notification +
'}'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.onesignal.notifications.internal.common

/** Why a notification is being shown again. */
internal enum class NotificationRestoreReason {
/** Android cleared the shade, such as after a reboot, app update, or force-stop. */
SHADE_RESTORE,

/**
* A group dropped to one member. Shown as a standalone notification, still in the shade,
* so the app's extender still applies.
*/
GROUP_REGROUP,
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ internal interface INotificationRepository {
*/
suspend fun markAsDismissed(androidId: Int): Boolean

/**
* Mark as dismissed without cancelling the notification from the shade.
*
* @param androidId The notification's Android ID
*
* @return true if a notification was marked as dismissed, false otherwise.
*/
suspend fun markAsDismissedWithoutCancel(androidId: Int): Boolean

suspend fun markAsDismissedForGroup(group: String)

suspend fun markAsDismissedForOutstanding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,27 @@ internal class NotificationRepository(
}
}

override suspend fun markAsDismissed(androidId: Int): Boolean {
override suspend fun markAsDismissed(androidId: Int): Boolean = markAsDismissed(androidId, cancelFromShade = true)

override suspend fun markAsDismissedWithoutCancel(androidId: Int): Boolean = markAsDismissed(androidId, cancelFromShade = false)

private suspend fun markAsDismissed(
androidId: Int,
cancelFromShade: Boolean,
): Boolean {
var didDismiss: Boolean = false

withContext(Dispatchers.IO) {
didDismiss = internalMarkAsDismissed(androidId)
didDismiss = internalMarkAsDismissed(androidId, cancelFromShade)
}

return didDismiss
}

private fun internalMarkAsDismissed(androidId: Int): Boolean {
val appContext = _applicationService.appContext

private fun internalMarkAsDismissed(
androidId: Int,
cancelFromShade: Boolean,
): Boolean {
val whereStr: String =
OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID.toString() + " = " + androidId + " AND " +
OneSignalDbContract.NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " +
Expand All @@ -146,8 +154,11 @@ internal class NotificationRepository(

_badgeCountUpdater.update()

val notificationManager: NotificationManager = NotificationHelper.getNotificationManager(appContext)
notificationManager.cancel(androidId)
if (cancelFromShade) {
val notificationManager: NotificationManager =
NotificationHelper.getNotificationManager(_applicationService.appContext)
notificationManager.cancel(androidId)
}

return didDismiss
}
Expand Down Expand Up @@ -424,7 +435,7 @@ internal class NotificationRepository(

while (it.moveToNext()) {
val existingId = it.getInt(OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)
internalMarkAsDismissed(existingId)
internalMarkAsDismissed(existingId, cancelFromShade = true)
if (--notificationsToClear <= 0) break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ internal class NotificationDisplayBuilder(
val fcmJson: JSONObject = notificationJob.jsonPayload!!
val oneSignalNotificationBuilder = OneSignalNotificationBuilder()
val channelId = _notificationChannelManager.createNotificationChannel(notificationJob)
oneSignalNotificationBuilder.channelId = channelId
val notificationBuilder = NotificationCompat.Builder(currentContext, channelId)
val message = fcmJson.optString("alert", null)
notificationBuilder
Expand Down Expand Up @@ -461,5 +462,8 @@ internal class NotificationDisplayBuilder(
internal class OneSignalNotificationBuilder {
var compatBuilder: NotificationCompat.Builder? = null
var hasLargeIcon = false

// The channel OneSignal picked, before any extender could change it.
var channelId: String? = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ internal class NotificationDisplayer(

// Keeps notification from playing sound + vibrating again
if (notificationJob.isRestoring) {
// An extender may have changed the channel, and the channel controls alerting on O+.
oneSignalNotificationBuilder.channelId?.let {
Logging.verbose("Restoring notification $notificationId on channel $it")
notifBuilder?.setChannelId(it)
}
_notificationDisplayBuilder.removeNotifyOptions(notifBuilder)
}

Expand Down Expand Up @@ -200,7 +205,6 @@ internal class NotificationDisplayer(
NotificationCompat.Builder::class.java.getDeclaredField("mNotification")
mNotificationField.isAccessible = true
var mNotification = mNotificationField[notificationBuilder] as Notification
notificationJob.orgFlags = mNotification.flags
notificationJob.orgSound = mNotification.sound
notificationBuilder!!.extend(notificationJob.notification!!.notificationExtender!!)
mNotification = mNotificationField[notificationBuilder] as Notification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,12 @@ internal class SummaryNotificationDisplayer(
notificationJob: NotificationGenerationJob,
notifBuilder: NotificationCompat.Builder?,
): Notification {
// Includes Android 4.3 through 6.0.1. Android 7.1 handles this correctly without this.
// Android 4.2 and older just post the summary only.
// Needed on Android 5.0 through 6.0.1. Android 7.0 handles this correctly without this.
val singleNotifWorkArounds =
Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N && !notificationJob.isRestoring
Build.VERSION.SDK_INT < Build.VERSION_CODES.N && !notificationJob.isRestoring
if (singleNotifWorkArounds) {
if ((notificationJob.overriddenSound != null) &&
!notificationJob.overriddenSound!!
.equals(notificationJob.orgSound)
) {
val overriddenSound = notificationJob.overriddenSound
if (overriddenSound != null && overriddenSound != notificationJob.orgSound) {
notifBuilder!!.setSound(null)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.onesignal.notifications.internal.generation

import android.content.Context
import com.onesignal.notifications.internal.common.NotificationRestoreReason
import org.json.JSONObject

internal interface INotificationGenerationProcessor {
suspend fun processNotificationData(
context: Context,
androidNotificationId: Int,
jsonPayload: JSONObject,
isRestoring: Boolean,
restoreReason: NotificationRestoreReason?,
timestamp: Long,
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.onesignal.notifications.internal.generation

import android.content.Context
import com.onesignal.notifications.internal.common.NotificationRestoreReason
import org.json.JSONObject

internal interface INotificationGenerationWorkManager {
Expand All @@ -10,7 +11,7 @@ internal interface INotificationGenerationWorkManager {
androidNotificationId: Int,
jsonPayload: JSONObject?,
timestamp: Long,
isRestoring: Boolean,
restoreReason: NotificationRestoreReason?,
isHighPriority: Boolean,
): Boolean
}
Loading