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
2 changes: 2 additions & 0 deletions iterableapi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
api 'com.google.firebase:firebase-messaging:20.3.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation "androidx.security:security-crypto:1.1.0-alpha06"
implementation 'androidx.work:work-runtime:2.9.0'

testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:runner:1.6.2'
Expand All @@ -75,6 +76,7 @@ dependencies {
testImplementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.3'
testImplementation 'org.skyscreamer:jsonassert:1.5.0'
testImplementation 'androidx.work:work-testing:2.9.0'
testImplementation project(':iterableapi')

androidTestImplementation 'androidx.test:runner:1.6.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.google.firebase.messaging.RemoteMessage;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

public class IterableFirebaseMessagingService extends FirebaseMessagingService {
Expand Down Expand Up @@ -56,12 +57,13 @@
return false;
}

if (!IterableNotificationHelper.isGhostPush(extras)) {
boolean isGhostPush = IterableNotificationHelper.isGhostPush(extras);

if (!isGhostPush) {
if (!IterableNotificationHelper.isEmptyBody(extras)) {
IterableLogger.d(TAG, "Iterable push received " + messageData);
IterableNotificationBuilder notificationBuilder = IterableNotificationHelper.createNotification(
context.getApplicationContext(), extras);
new IterableNotificationManager().execute(notificationBuilder);

enqueueNotificationWork(context, extras, false);
} else {
IterableLogger.d(TAG, "Iterable OS notification push received");
}
Expand Down Expand Up @@ -105,9 +107,7 @@
String registrationToken = null;
try {
registrationToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
} catch (ExecutionException e) {
IterableLogger.e(TAG, e.getLocalizedMessage());
} catch (InterruptedException e) {
} catch (ExecutionException | InterruptedException e) {
IterableLogger.e(TAG, e.getLocalizedMessage());
} catch (Exception e) {
IterableLogger.e(TAG, "Failed to fetch firebase token");
Expand All @@ -122,17 +122,64 @@
* @return Boolean indicating whether the message is an Iterable ghost push or silent push
*/
public static boolean isGhostPush(RemoteMessage remoteMessage) {
Map<String, String> messageData = remoteMessage.getData();
try {
Map<String, String> messageData = remoteMessage.getData();

if (messageData == null || messageData.isEmpty()) {
if (messageData.isEmpty()) {
return false;
}

Bundle extras = IterableNotificationHelper.mapToBundle(messageData);
return IterableNotificationHelper.isGhostPush(extras);
} catch (Exception e) {
IterableLogger.e(TAG, e.getMessage());
return false;
}
}

Bundle extras = IterableNotificationHelper.mapToBundle(messageData);
return IterableNotificationHelper.isGhostPush(extras);
private static void enqueueNotificationWork(@NonNull final Context context, @NonNull final Bundle extras, boolean isGhostPush) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'isGhostPush' is never used.
IterableNotificationWorkScheduler scheduler = new IterableNotificationWorkScheduler(context);

scheduler.scheduleNotificationWork(
extras,
false,
new IterableNotificationWorkScheduler.SchedulerCallback() {
@Override
public void onScheduleSuccess(UUID workId) {
IterableLogger.d(TAG, "Notification work scheduled: " + workId);
}

@Override
public void onScheduleFailure(Exception exception, Bundle notificationData) {
IterableLogger.e(TAG, "Failed to schedule notification work, falling back", exception);
handleFallbackNotification(context, notificationData);
}
}
);
}

private static void handleFallbackNotification(@NonNull Context context, @NonNull Bundle extras) {
try {
IterableNotificationBuilder notificationBuilder = IterableNotificationHelper.createNotification(
context.getApplicationContext(), extras);
if (notificationBuilder != null) {
IterableNotificationHelper.postNotificationOnDevice(context, notificationBuilder);
IterableLogger.d(TAG, "✓ FALLBACK succeeded - notification posted directly");
} else {
IterableLogger.w(TAG, "✗ FALLBACK: Notification builder is null");
}
} catch (Exception fallbackException) {
IterableLogger.e(TAG, "✗ CRITICAL: FALLBACK also failed!", fallbackException);
IterableLogger.e(TAG, "NOTIFICATION WILL NOT BE DISPLAYED");
}
}
}

/**
* @deprecated This class is no longer used. Notification processing now uses WorkManager
* to comply with Firebase best practices. This class is kept for backwards compatibility only.
*/
@Deprecated
class IterableNotificationManager extends AsyncTask<IterableNotificationBuilder, Void, Void> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ static Bundle mapToBundle(Map<String, String> map) {
static class IterableNotificationHelperImpl {

public IterableNotificationBuilder createNotification(Context context, Bundle extras) {
if (extras == null) {
IterableLogger.w(IterableNotificationBuilder.TAG, "Notification extras is null. Skipping.");
return null;
}

String applicationName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString();
String title = null;
String notificationBody = null;
Expand Down Expand Up @@ -436,7 +441,7 @@ boolean isIterablePush(Bundle extras) {

boolean isGhostPush(Bundle extras) {
boolean isGhostPush = false;
if (extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) {
if (extras != null && extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) {
String iterableData = extras.getString(IterableConstants.ITERABLE_DATA_KEY);
IterableNotificationData data = new IterableNotificationData(iterableData);
isGhostPush = data.getIsGhostPush();
Expand All @@ -447,7 +452,7 @@ boolean isGhostPush(Bundle extras) {

boolean isEmptyBody(Bundle extras) {
String notificationBody = "";
if (extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) {
if (extras != null && extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) {
notificationBody = extras.getString(IterableConstants.ITERABLE_DATA_BODY, "");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.iterable.iterableapi;

import android.content.Context;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;

import java.util.UUID;

class IterableNotificationWorkScheduler {

private static final String TAG = "IterableNotificationWorkScheduler";

private final Context context;
private final WorkManager workManager;

interface SchedulerCallback {
void onScheduleSuccess(UUID workId);
void onScheduleFailure(Exception exception, Bundle notificationData);
}

IterableNotificationWorkScheduler(@NonNull Context context) {
this(context, WorkManager.getInstance(context));
}

@VisibleForTesting
IterableNotificationWorkScheduler(@NonNull Context context, @NonNull WorkManager workManager) {
this.context = context.getApplicationContext();
this.workManager = workManager;
}

public void scheduleNotificationWork(
@NonNull Bundle notificationData,
boolean isGhostPush,
@Nullable SchedulerCallback callback) {

try {
androidx.work.Data inputData = IterableNotificationWorker.createInputData(
notificationData,
isGhostPush
);

OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(IterableNotificationWorker.class)
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();

workManager.enqueue(workRequest);

UUID workId = workRequest.getId();
IterableLogger.d(TAG, "Notification work scheduled: " + workId);

if (callback != null) {
callback.onScheduleSuccess(workId);
}

} catch (Exception e) {
IterableLogger.e(TAG, "Failed to schedule notification work", e);

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

@VisibleForTesting
Context getContext() {
return context;
}

@VisibleForTesting
WorkManager getWorkManager() {
return workManager;
}
}
Loading
Loading