11package com.iterable.iterableapi
22
3+ import android.app.NotificationChannel
4+ import android.app.NotificationManager
35import android.content.Context
4- import android.graphics.Bitmap
5- import android.graphics.BitmapFactory
6+ import android.content.pm.PackageManager
7+ import android.os.Build
68import android.os.Bundle
79import androidx.annotation.WorkerThread
10+ import androidx.core.app.NotificationCompat
811import androidx.work.Data
12+ import androidx.work.ForegroundInfo
913import androidx.work.Worker
1014import androidx.work.WorkerParameters
1115import org.json.JSONObject
12- import java.io.IOException
13- import java.net.URL
14-
15- /* *
16- * WorkManager Worker to handle push notification processing.
17- * This replaces the deprecated AsyncTask approach to comply with Firebase best practices.
18- *
19- * The Worker handles:
20- * - Downloading notification images from remote URLs
21- * - Building notifications with proper styling
22- * - Posting notifications to the system
23- */
16+
2417internal class IterableNotificationWorker (
2518 context : Context ,
2619 params : WorkerParameters
2720) : Worker(context, params) {
2821
2922 companion object {
3023 private const val TAG = " IterableNotificationWorker"
31-
24+ private const val FOREGROUND_NOTIFICATION_ID = 10101
25+
3226 const val KEY_NOTIFICATION_DATA_JSON = " notification_data_json"
3327 const val KEY_IS_GHOST_PUSH = " is_ghost_push"
34-
35- /* *
36- * Creates input data for the Worker from a Bundle.
37- * Converts the Bundle to JSON for reliable serialization.
38- */
28+
3929 @JvmStatic
4030 fun createInputData (extras : Bundle , isGhostPush : Boolean ): Data {
4131 val jsonObject = JSONObject ()
@@ -45,84 +35,129 @@ internal class IterableNotificationWorker(
4535 jsonObject.put(key, value)
4636 }
4737 }
48-
38+
4939 return Data .Builder ()
5040 .putString(KEY_NOTIFICATION_DATA_JSON , jsonObject.toString())
5141 .putBoolean(KEY_IS_GHOST_PUSH , isGhostPush)
5242 .build()
5343 }
5444 }
5545
46+ override fun getForegroundInfo (): ForegroundInfo {
47+ val channelId = applicationContext.packageName
48+
49+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
50+ val notificationManager = applicationContext
51+ .getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
52+ if (notificationManager.getNotificationChannel(channelId) == null ) {
53+ val channel = NotificationChannel (
54+ channelId,
55+ getChannelName(),
56+ NotificationManager .IMPORTANCE_LOW
57+ )
58+ notificationManager.createNotificationChannel(channel)
59+ }
60+ }
61+
62+ val notification = NotificationCompat .Builder (applicationContext, channelId)
63+ .setSmallIcon(getSmallIconId())
64+ .setContentTitle(getAppName())
65+ .setPriority(NotificationCompat .PRIORITY_LOW )
66+ .build()
67+
68+ return ForegroundInfo (FOREGROUND_NOTIFICATION_ID , notification)
69+ }
70+
71+ private fun getSmallIconId (): Int {
72+ var iconId = 0
73+
74+ try {
75+ val info = applicationContext.packageManager.getApplicationInfo(
76+ applicationContext.packageName, PackageManager .GET_META_DATA
77+ )
78+ iconId = info.metaData?.getInt(IterableConstants .NOTIFICATION_ICON_NAME , 0 ) ? : 0
79+ } catch (e: PackageManager .NameNotFoundException ) {
80+ IterableLogger .w(TAG , " Could not read application metadata for icon" )
81+ }
82+
83+ if (iconId == 0 ) {
84+ iconId = applicationContext.resources.getIdentifier(
85+ IterableApi .getNotificationIcon(applicationContext),
86+ IterableConstants .ICON_FOLDER_IDENTIFIER ,
87+ applicationContext.packageName
88+ )
89+ }
90+
91+ if (iconId == 0 ) {
92+ iconId = applicationContext.applicationInfo.icon
93+ }
94+
95+ return iconId
96+ }
97+
98+ private fun getAppName (): String {
99+ return applicationContext.applicationInfo
100+ .loadLabel(applicationContext.packageManager).toString()
101+ }
102+
103+ private fun getChannelName (): String {
104+ return try {
105+ val info = applicationContext.packageManager.getApplicationInfo(
106+ applicationContext.packageName, PackageManager .GET_META_DATA
107+ )
108+ info.metaData?.getString(" iterable_notification_channel_name" )
109+ ? : " Notifications"
110+ } catch (e: PackageManager .NameNotFoundException ) {
111+ " Notifications"
112+ }
113+ }
114+
56115 @WorkerThread
57116 override fun doWork (): Result {
58- IterableLogger .d(TAG , " ========================================" )
59117 IterableLogger .d(TAG , " Starting notification processing in Worker" )
60- IterableLogger .d(TAG , " Worker ID: $id " )
61- IterableLogger .d(TAG , " Run attempt: $runAttemptCount " )
62-
118+
63119 try {
64120 val isGhostPush = inputData.getBoolean(KEY_IS_GHOST_PUSH , false )
65- IterableLogger .d(TAG , " Step 1: Ghost push check - isGhostPush=$isGhostPush " )
66-
121+
67122 if (isGhostPush) {
68- IterableLogger .d(TAG , " Ghost push detected - no user-visible notification to display" )
123+ IterableLogger .d(TAG , " Ghost push detected, skipping notification display" )
69124 return Result .success()
70125 }
71126
72127 val jsonString = inputData.getString(KEY_NOTIFICATION_DATA_JSON )
73- IterableLogger .d(TAG , " Step 2: Retrieved notification JSON data (length=${jsonString?.length ? : 0 } )" )
74-
128+
75129 if (jsonString == null || jsonString.isEmpty()) {
76- IterableLogger .e(TAG , " CRITICAL ERROR: No notification data provided to Worker" )
130+ IterableLogger .e(TAG , " No notification data provided to Worker" )
77131 return Result .failure()
78132 }
79133
80- IterableLogger .d(TAG , " Step 3: Deserializing notification data from JSON" )
81134 val extras = jsonToBundle(jsonString)
82- val keyCount = extras.keySet().size
83- IterableLogger .d(TAG , " Step 3: Deserialized $keyCount keys from notification data" )
84-
85- if (keyCount == 0 ) {
86- IterableLogger .e(TAG , " CRITICAL ERROR: Deserialized bundle is empty" )
135+
136+ if (extras.keySet().size == 0 ) {
137+ IterableLogger .e(TAG , " Deserialized bundle is empty" )
87138 return Result .failure()
88139 }
89140
90- IterableLogger .d(TAG , " Step 4: Creating notification builder" )
91141 val notificationBuilder = IterableNotificationHelper .createNotification(
92142 applicationContext,
93143 extras
94144 )
95-
145+
96146 if (notificationBuilder == null ) {
97- IterableLogger .w(TAG , " Step 4: Notification builder is null (likely ghost push or invalid data) " )
147+ IterableLogger .w(TAG , " Notification builder is null, skipping " )
98148 return Result .success()
99149 }
100-
101- IterableLogger .d(TAG , " Step 4: Notification builder created successfully" )
102- val hasImage = extras.getString(IterableConstants .ITERABLE_DATA_PUSH_IMAGE ) != null
103- if (hasImage) {
104- IterableLogger .d(TAG , " Step 4: Notification contains image URL: ${extras.getString(IterableConstants .ITERABLE_DATA_PUSH_IMAGE )} " )
105- }
106150
107- IterableLogger .d(TAG , " Step 5: Posting notification to device (this may download images)" )
108151 IterableNotificationHelper .postNotificationOnDevice(
109152 applicationContext,
110153 notificationBuilder
111154 )
112-
113- IterableLogger .d(TAG , " Step 5: Notification posted successfully to NotificationManager" )
114- IterableLogger .d(TAG , " Notification processing COMPLETED successfully" )
115- IterableLogger .d(TAG , " ========================================" )
155+
156+ IterableLogger .d(TAG , " Notification posted successfully" )
116157 return Result .success()
117-
158+
118159 } catch (e: Exception ) {
119- IterableLogger .e(TAG , " ========================================" )
120- IterableLogger .e(TAG , " CRITICAL ERROR processing notification in Worker" , e)
121- IterableLogger .e(TAG , " Error type: ${e.javaClass.simpleName} " )
122- IterableLogger .e(TAG , " Error message: ${e.message} " )
123- IterableLogger .e(TAG , " Stack trace:" , e)
124- IterableLogger .e(TAG , " ========================================" )
125-
160+ IterableLogger .e(TAG , " Error processing notification in Worker" , e)
126161 return Result .retry()
127162 }
128163 }
0 commit comments