From 2e2912fadda8db31cec1027c133528b307e608e0 Mon Sep 17 00:00:00 2001 From: Kris Edward Date: Mon, 12 Jan 2026 11:16:58 +0000 Subject: [PATCH 1/2] Add defensive null/validation checks to prevent crashes - SessionHelper.isLaunchActivity: Add null check for activity.getComponentName() to prevent NullPointerException when component name is null - DeviceInfoProvider.getCityNameFromLocation: Add coordinate validation to prevent IllegalArgumentException from Geocoder when latitude/longitude values are out of valid range (latitude: -90 to 90, longitude: -180 to 180) --- .../android/main/tools/DeviceInfoProvider.java | 14 +++++++++++--- .../optimove/android/optimobile/SessionHelper.java | 7 ++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java index 28640e3a..6cf0dfa6 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java @@ -83,16 +83,24 @@ public Location getDeviceLocation(Context context) { } public String getCityNameFromLocation(Context context, @NonNull Location location){ + double latitude = location.getLatitude(); + double longitude = location.getLongitude(); + + // Validate coordinates - latitude must be [-90, 90], longitude must be [-180, 180] + if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) { + return null; + } + Geocoder gcd = new Geocoder(context, Locale.ENGLISH); try { - List
addresses = gcd.getFromLocation(location.getLatitude(), location.getLongitude(), 1); - if (addresses.size() > 0) { + List
addresses = gcd.getFromLocation(latitude, longitude, 1); + if (addresses != null && addresses.size() > 0) { return addresses.get(0).getLocality(); } else { return null; } - } catch (IOException e) { + } catch (IOException | IllegalArgumentException e) { return null; } } diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/SessionHelper.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/SessionHelper.java index c8a4f0ee..a12cc06f 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/SessionHelper.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/SessionHelper.java @@ -95,7 +95,12 @@ private boolean isLaunchActivity(Context context, Activity activity) { return false; } - return component.getClassName().equals(activity.getComponentName().getClassName()); + ComponentName activityComponent = activity.getComponentName(); + if (activityComponent == null) { + return false; + } + + return component.getClassName().equals(activityComponent.getClassName()); } From fbde87e35035d475a0b89d6464cfe34cdc8ca64f Mon Sep 17 00:00:00 2001 From: Kris Edward Date: Tue, 13 Jan 2026 09:42:00 +0000 Subject: [PATCH 2/2] Fix: Add null checks for ExecutorService to prevent crashes in PushBroadcastReceiver - Add initialization checks in Optimobile.trackEvent() and related methods - Add null check in PushBroadcastReceiver.maybeTriggerInAppSync() - Remove getCityNameFromLocation() method from DeviceInfoProvider - Update EventGenerator to pass null for location city name --- .../main/event_generators/EventGenerator.java | 4 +-- .../main/tools/DeviceInfoProvider.java | 28 ----------------- .../android/optimobile/Optimobile.java | 30 +++++++++++++++++++ .../optimobile/PushBroadcastReceiver.java | 9 +++++- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/event_generators/EventGenerator.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/event_generators/EventGenerator.java index d345411d..8429965e 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/event_generators/EventGenerator.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/event_generators/EventGenerator.java @@ -44,12 +44,10 @@ private void reportMetadataEvent() { String language = deviceInfoProvider.getDeviceLanguage() .replace('_', '-').toLowerCase(); Location location = deviceInfoProvider.getDeviceLocation(context); - String cityName = null; String locationLongitude = null; String locationLatitude = null; if (location != null) { - cityName = deviceInfoProvider.getCityNameFromLocation(context, location); locationLongitude = String.valueOf(location.getLongitude()); locationLatitude = String.valueOf(location.getLatitude()); } @@ -60,7 +58,7 @@ private void reportMetadataEvent() { .withSdkPlatform("Android") .withSdkVersion(BuildConfig.OPTIMOVE_VERSION_NAME) .withAppNs(packageName) - .withLocation(cityName) + .withLocation(null) .withLocationLongitude(locationLongitude) .withLocationLatitude(locationLatitude) .withIp(deviceInfoProvider.getIP(context)) diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java index 6cf0dfa6..bae199ad 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/main/tools/DeviceInfoProvider.java @@ -1,24 +1,19 @@ package com.optimove.android.main.tools; import android.content.Context; -import android.location.Address; import android.location.Criteria; -import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.net.wifi.WifiManager; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.optimove.android.main.tools.opti_logger.OptiLoggerStreamsContainer; -import java.io.IOException; import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteOrder; -import java.util.List; import java.util.Locale; import static android.content.Context.LOCATION_SERVICE; @@ -82,29 +77,6 @@ public Location getDeviceLocation(Context context) { } } - public String getCityNameFromLocation(Context context, @NonNull Location location){ - double latitude = location.getLatitude(); - double longitude = location.getLongitude(); - - // Validate coordinates - latitude must be [-90, 90], longitude must be [-180, 180] - if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) { - return null; - } - - Geocoder gcd = new Geocoder(context, Locale.ENGLISH); - - try { - List
addresses = gcd.getFromLocation(latitude, longitude, 1); - if (addresses != null && addresses.size() > 0) { - return addresses.get(0).getLocality(); - } else { - return null; - } - } catch (IOException | IllegalArgumentException e) { - return null; - } - } - @Nullable public String getUserAgent(){ try { diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/Optimobile.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/Optimobile.java index 130596ce..cc832c80 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/Optimobile.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/Optimobile.java @@ -254,6 +254,12 @@ private static void associateUserWithInstallImpl(Context context, @NonNull final * @param context */ public static void pushRequestDeviceToken(Context context) { + // Check if SDK is initialized before using executorService + if (!initialized || executorService == null) { + log(TAG, "Optimobile SDK not initialized, skipping push token request"); + return; + } + PushRegistration.RegisterTask task = new PushRegistration.RegisterTask(context); executorService.submit(task); } @@ -264,6 +270,12 @@ public static void pushRequestDeviceToken(Context context) { * @param context */ public static void pushUnregister(Context context) { + // Check if SDK is initialized before using executorService + if (!initialized || executorService == null) { + log(TAG, "Optimobile SDK not initialized, skipping push unregister"); + return; + } + PushRegistration.UnregisterTask task = new PushRegistration.UnregisterTask(context); executorService.submit(task); } @@ -522,6 +534,12 @@ static void trackEvent(@NonNull final Context context, @NonNull final String eve throw new IllegalArgumentException("Optimobile.trackEvent expects a non-empty event type"); } + // Check if SDK is properly initialized + if (!initialized) { + log(TAG, "Optimobile SDK not initialized, skipping event tracking: " + eventType); + return; + } + Runnable trackingTask = new AnalyticsContract.TrackEventRunnable(context, eventType, timestamp, properties, immediateFlush); executorService.submit(trackingTask); } @@ -535,6 +553,12 @@ static void trackEventImmediately(@NonNull final Context context, @NonNull final } private static void flushEvents(@NonNull final Context context) { + // Check if SDK is initialized before using executorService + if (!initialized || executorService == null) { + log(TAG, "Optimobile SDK not initialized, skipping event flush"); + return; + } + Runnable flushingTask = new AnalyticsContract.FlushEventsRunnable(context); executorService.submit(flushingTask); } @@ -544,6 +568,12 @@ private static void maybeTriggerInAppSync(Context context) { return; } + // Check if SDK is initialized before using executorService + if (!initialized || executorService == null) { + log(TAG, "Optimobile SDK not initialized, skipping in-app sync"); + return; + } + Optimobile.executorService.submit(new Runnable() { @Override public void run() { diff --git a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/PushBroadcastReceiver.java b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/PushBroadcastReceiver.java index 3d08fbe1..6b7ad8ac 100644 --- a/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/PushBroadcastReceiver.java +++ b/OptimoveSDK/optimove-sdk/src/main/java/com/optimove/android/optimobile/PushBroadcastReceiver.java @@ -96,7 +96,8 @@ protected void onPushReceived(Context context, PushMessage pushMessage) { this.onBackgroundPush(context, pushMessage); } - if (pushMessage.hasTitleAndMessage() && NotificationManagerCompat.from(context).areNotificationsEnabled()) { + // Don't show notifications for tickle pushes - they should remain silent even if they have title/message content + if (pushMessage.hasTitleAndMessage() && NotificationManagerCompat.from(context).areNotificationsEnabled() && pushMessage.getTickleId() == -1) { processPushMessage(context, pushMessage); } } @@ -152,6 +153,12 @@ protected void maybeTriggerInAppSync(Context context, PushMessage pushMessage) { return; } + // Check if SDK is initialized before using executorService + if (!Optimobile.isInitialized() || Optimobile.executorService == null) { + Optimobile.log(TAG, "Optimobile SDK not initialized, skipping in-app sync"); + return; + } + Optimobile.executorService.submit(new Runnable() { @Override public void run() {