Skip to content
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,23 @@ interface INotificationReceivedEvent {
*/
val notification: IDisplayableMutableNotification

/**
* True when OneSignal is showing this notification again after your app already received it.
* Happens after a reboot, app update, or cold start following a force-stop, 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. Channel and sound are the exception. OneSignal puts
* a restored notification back on its silent Restored channel, so it never alerts again.
*
* Call `preventDefault(true)` to stop a restored notification from coming back. A
* group-collapse rebuild still showing in the shade is left in place; one that is not
* showing is marked dismissed so it stays gone. The no-argument [preventDefault] waits up
* to 30 seconds for `notification.display()`.
Comment thread
nan-li marked this conversation as resolved.
*/
val restoring: Boolean
get() = false
Comment thread
nan-li marked this conversation as resolved.

/**
* 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
Expand Up @@ -42,6 +42,18 @@ object NotificationHelper {
return statusBarNotifications
}

/**
* Whether this notification is currently posted in the shade. Below Android M there is no
* API to ask, so it is assumed to still be posted.
*/
fun isNotificationActive(
context: Context,
androidNotificationId: Int,
): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true
return getActiveNotifications(context).any { it.id == androidNotificationId }
Comment thread
nan-li marked this conversation as resolved.
}

/**
* Iterate over all active notifications and count the groupless ones
* and return the int count
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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, which is re-posted so it looks standalone. Normally still
* in the shade. The extender applies, but channel and sound are forced back to the silent
* restore channel, the same as any restore.
*/
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 @@ -144,10 +152,15 @@ internal class NotificationRepository(

val didDismiss = records > 0

// Refresh even when the notification stays in the shade. Below API 23 the badge counts
// undismissed rows, so it drops here while the notification is still showing.
_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 +437,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
Loading