Skip to content

Commit bfd33a5

Browse files
Compatibility with versions prior to Android 12
1 parent 73d39aa commit bfd33a5

File tree

4 files changed

+185
-139
lines changed

4 files changed

+185
-139
lines changed

iterableapi/src/main/java/com/iterable/iterableapi/IterableFirebaseMessagingService.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,16 @@ private static void enqueueNotificationWork(@NonNull final Context context, @Non
142142

143143
scheduler.scheduleNotificationWork(
144144
extras,
145-
isGhostPush,
145+
false,
146146
new IterableNotificationWorkScheduler.SchedulerCallback() {
147147
@Override
148148
public void onScheduleSuccess(UUID workId) {
149-
IterableLogger.d(TAG, "Notification work scheduled successfully: " + workId);
149+
IterableLogger.d(TAG, "Notification work scheduled: " + workId);
150150
}
151151

152152
@Override
153153
public void onScheduleFailure(Exception exception, Bundle notificationData) {
154-
IterableLogger.e(TAG, "Failed to schedule notification work", exception);
155-
IterableLogger.e(TAG, "Attempting FALLBACK to direct processing...");
154+
IterableLogger.e(TAG, "Failed to schedule notification work, falling back", exception);
156155
handleFallbackNotification(context, notificationData);
157156
}
158157
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableNotificationWorkScheduler.java

Lines changed: 2 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -12,121 +12,55 @@
1212

1313
import java.util.UUID;
1414

15-
/**
16-
* Manages scheduling of notification processing work using WorkManager.
17-
* This class is responsible for:
18-
* - Creating WorkManager requests for notification processing
19-
* - Enqueueing work with expedited execution for high-priority notifications
20-
* - Providing callback interface for success/failure handling
21-
* - Comprehensive logging of scheduling operations
22-
*/
2315
class IterableNotificationWorkScheduler {
2416

2517
private static final String TAG = "IterableNotificationWorkScheduler";
2618

2719
private final Context context;
2820
private final WorkManager workManager;
2921

30-
/**
31-
* Callback interface for work scheduling results.
32-
* Allows caller to handle success/failure appropriately.
33-
*/
3422
interface SchedulerCallback {
35-
/**
36-
* Called when work is successfully scheduled.
37-
* @param workId UUID of the scheduled work
38-
*/
3923
void onScheduleSuccess(UUID workId);
40-
41-
/**
42-
* Called when work scheduling fails.
43-
* @param exception The exception that caused the failure
44-
* @param notificationData The original notification data (for fallback)
45-
*/
4624
void onScheduleFailure(Exception exception, Bundle notificationData);
4725
}
4826

49-
/**
50-
* Constructor for production use.
51-
* Initializes with application context and default WorkManager instance.
52-
*
53-
* @param context Application or service context
54-
*/
5527
IterableNotificationWorkScheduler(@NonNull Context context) {
5628
this(context, WorkManager.getInstance(context));
5729
}
5830

59-
/**
60-
* Constructor for testing.
61-
* Allows injection of mock WorkManager for unit testing.
62-
*
63-
* @param context Application or service context
64-
* @param workManager WorkManager instance (can be mocked for tests)
65-
*/
6631
@VisibleForTesting
6732
IterableNotificationWorkScheduler(@NonNull Context context, @NonNull WorkManager workManager) {
6833
this.context = context.getApplicationContext();
6934
this.workManager = workManager;
7035
}
7136

72-
/**
73-
* Schedules notification processing work using WorkManager.
74-
*
75-
* Creates an expedited OneTimeWorkRequest and enqueues it with WorkManager.
76-
* Expedited execution ensures high-priority notifications are processed promptly,
77-
* with quota exemption when called from FCM onMessageReceived.
78-
*
79-
* @param notificationData Bundle containing notification data
80-
* @param isGhostPush Whether this is a ghost/silent push
81-
* @param callback Optional callback for success/failure (can be null)
82-
*/
8337
public void scheduleNotificationWork(
8438
@NonNull Bundle notificationData,
8539
boolean isGhostPush,
8640
@Nullable SchedulerCallback callback) {
8741

88-
IterableLogger.d(TAG, "========================================");
89-
IterableLogger.d(TAG, "Scheduling notification work");
90-
IterableLogger.d(TAG, "Bundle keys: " + notificationData.keySet().size());
91-
IterableLogger.d(TAG, "Is ghost push: " + isGhostPush);
92-
9342
try {
94-
IterableLogger.d(TAG, "Step 1: Creating Worker input data");
9543
androidx.work.Data inputData = IterableNotificationWorker.createInputData(
9644
notificationData,
9745
isGhostPush
9846
);
99-
IterableLogger.d(TAG, " ✓ Worker input data created successfully");
10047

101-
IterableLogger.d(TAG, "Step 2: Building expedited WorkRequest");
10248
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(IterableNotificationWorker.class)
10349
.setInputData(inputData)
10450
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
10551
.build();
106-
IterableLogger.d(TAG, " ✓ WorkRequest built with expedited execution");
10752

108-
IterableLogger.d(TAG, "Step 3: Enqueueing work with WorkManager");
10953
workManager.enqueue(workRequest);
11054

11155
UUID workId = workRequest.getId();
112-
IterableLogger.d(TAG, " ✓ Work enqueued successfully");
113-
IterableLogger.d(TAG, "");
114-
IterableLogger.d(TAG, "✓ NOTIFICATION WORK SCHEDULED");
115-
IterableLogger.d(TAG, " Work ID: " + workId);
116-
IterableLogger.d(TAG, " Priority: EXPEDITED (high-priority notification)");
117-
IterableLogger.d(TAG, " Worker: " + IterableNotificationWorker.class.getSimpleName());
118-
IterableLogger.d(TAG, "========================================");
56+
IterableLogger.d(TAG, "Notification work scheduled: " + workId);
11957

12058
if (callback != null) {
12159
callback.onScheduleSuccess(workId);
12260
}
12361

12462
} catch (Exception e) {
125-
IterableLogger.e(TAG, "========================================");
126-
IterableLogger.e(TAG, "✗ FAILED TO SCHEDULE NOTIFICATION WORK");
127-
IterableLogger.e(TAG, "Error type: " + e.getClass().getSimpleName());
128-
IterableLogger.e(TAG, "Error message: " + e.getMessage());
129-
IterableLogger.e(TAG, "========================================");
63+
IterableLogger.e(TAG, "Failed to schedule notification work", e);
13064

13165
if (callback != null) {
13266
callback.onScheduleFailure(e, notificationData);

iterableapi/src/main/java/com/iterable/iterableapi/IterableNotificationWorker.kt

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,31 @@
11
package com.iterable.iterableapi
22

3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
35
import android.content.Context
4-
import android.graphics.Bitmap
5-
import android.graphics.BitmapFactory
6+
import android.content.pm.PackageManager
7+
import android.os.Build
68
import android.os.Bundle
79
import androidx.annotation.WorkerThread
10+
import androidx.core.app.NotificationCompat
811
import androidx.work.Data
12+
import androidx.work.ForegroundInfo
913
import androidx.work.Worker
1014
import androidx.work.WorkerParameters
1115
import 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+
2417
internal 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

Comments
 (0)