Humble Roots
Humble Roots

Reputation: 9

Alarm Manager Behavior Doesn't Match Logs

I'm trying to write an app that keeps track of multiple lists of perishable goods from internal storage. I'm storing and reading serialized .json files to edit and save lists etc., but I've been extremely stuck when it comes to implementing a good notification scheme..

I'm calculating the notifytimemillis with the expiration date and the days notice value (days notice is a number of days between then and the expiration date a user wishes to be informed of spoilage). For whatever reason, no matter what I do, most of the notifications fire instantly.

Here is the code, I'm referring to:

private void scheduleAlarms(Context context, JSONArray inventory, String listName)
    {

        int DAYS_BEFORE_EXPIRY = 7; // Notify 7 days before expiration
        long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000; // 1 day in milliseconds

        // Set up the date formatter (force UTC if needed)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
        //sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        for (int i = 0; i < inventory.length(); i++) {
            try {
                JSONObject item = inventory.getJSONObject(i);
                String name = item.getString("name");
                String expDateString = item.getString("expDate");

                Date expDate = sdf.parse(expDateString); // Set to end of day (23:59:59)

                long expDateMillis = expDate.getTime();

                long daysNoticeMillis = item.getInt("daysNotice") * MILLIS_IN_A_DAY;
                long expirationThreshold = expDateMillis - daysNoticeMillis;

                Log.d("EXP DATE MILLIS:", String.valueOf(expDateMillis));
                Log.d("DAYS NOTICE MILLIS:", String.valueOf(daysNoticeMillis));

                long notifyTimeMillis = expDateMillis - (DAYS_BEFORE_EXPIRY * MILLIS_IN_A_DAY);
                Log.d("NOTIFY TIME MILLIS:", String.valueOf(notifyTimeMillis));

                long currentTimeMillis = System.currentTimeMillis();

                // Only schedule alarms if we're within the notice window
                if (currentTimeMillis >= expirationThreshold)
                {
                    Intent intent = new Intent(context, NotificationReceiver.class);

                    String expiredOrGoing = "";
                    String title = "";

                    if (currentTimeMillis > expDateMillis)
                    {
                        expiredOrGoing = name + " has expired!";
                        title = "\uD83D\uDD34 Expiration notice for " + listName + " list!";
                    }

                    else if (currentTimeMillis >= expirationThreshold)
                    {
                        expiredOrGoing = name + " will expire soon!";
                        title = "\uD83D\uDFE0 Going notice for " + listName + " list!";
                    }
                    else if (currentTimeMillis < expirationThreshold)
                    {expiredOrGoing = name + " is safe for now!";}


                    Log.d("Debug", name + ": " + expiredOrGoing);


                    intent.putExtra("itemData", item.toString());
                    intent.putExtra("titleExtra", title);
                    intent.putExtra("messageExtra", expiredOrGoing);

                    // Use a composite unique identifier
                    String uniqueKey = listName + "_" + name; // Combine list and item name
                    int uniqueID = uniqueKey.hashCode();

                    // Create a new PendingIntent
                    PendingIntent pendingIntent = PendingIntent.getBroadcast(
                            context, uniqueID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
                    );

                    Log.d("Debug", "Attempting to schedule notification for " + name + " at: " + notifyTimeMillis);

                    //Log.d("Debug", name + " - Expiration Date: " + expDateMillis);
                    //Log.d("Debug", name + " - daysNoticeMillis: " + daysNoticeMillis);
                    //Log.d("Debug", name + " - Notify time: " + notifyTimeMillis);

                    /*if (notifyTimeMillis < System.currentTimeMillis()) {
                        Log.e("AlarmError", "⛔ Not scheduling alarm for past date: " + name + ", " + notifyTimeMillis);
                        continue;
                    }*/

                    // Parsing the expiration date string into a Date object

                    Log.d("NOTIFY TIME MILLIS:", String.valueOf(notifyTimeMillis));

                    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

                    // Schedule the alarm
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                    {
                        long bdate = new Long("1898978269000");

                        //alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, bdate, pendingIntent);
                        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, notifyTimeMillis, pendingIntent);

                    } else
                    {
                        alarmManager.setExact(AlarmManager.RTC_WAKEUP, notifyTimeMillis, pendingIntent);
                    }

                    Log.d("Alarm", "Scheduled notification for " + name + " at " + notifyTimeMillis);
                }
                else {Log.d("Debug", name + " - Notify time (" + notifyTimeMillis + ") has already passed. (Optionally trigger immediately or skip.)");}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

This is the calculation logic I want to use, but when I use this line, not only do the notifications still fire instantly, but also not all the notifications come through, particularly the notification for "coke":

"long expirationThreshold = expDateMillis - (DAYS_BEFORE_EXPIRY * MILLIS_IN_A_DAY);"

The difference between that calculation I want vs. this one I'm using is the coke item being the only one to actually get scheduled correctly in the former vs. all of them firing immediately with the latter.

long notifyTimeMillis = expDateMillis - expirationThreshold;

I've been stuck for like three days now, so if you can help: thanks in advance!

Here are the logs from when I change the calculation to be based on the DAYS_BEFORE_EXPIRY which

I originally had some comparative logic there to schedule a reminder based on how far out you were, and my worker used to have a periodic request instead of a onetimerequest so that the alarms would be scheduled every 24h if the item were still going but not yet expired. So you'd have an alarm at 14 days before, then 7, then 3 then 1. 2 week, 1 week, 3 days and 1 day reminder.

but I got rid of all that code to hunker down and debug this, so now I've got the oneTimeRequest and here are the logs when I use the above calculation formula:

2025-02-27 23:13:15.392 10185-10185 WM-WorkerWrapper        sma...tory.smartinventorywithsearch  D  Starting work for smart.inventory.smartinventorywithsearch.ExpirationCheckWorker
2025-02-27 23:13:15.393 10185-10340 Worker                  sma...tory.smartinventorywithsearch  D  Directories found: [/data/user/0/smart.inventory.smartinventorywithsearch/files/Smart Inventory/All Lists/misc, /data/user/0/smart.inventory.smartinventorywithsearch/files/Smart Inventory/All Lists/games]
2025-02-27 23:13:15.394 10185-10340 EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  1741237200000
2025-02-27 23:13:15.394 10185-10340 DAYS NOTICE MILLIS:     sma...tory.smartinventorywithsearch  D  8640000000
2025-02-27 23:13:15.394 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1740632400000
2025-02-27 23:13:15.394 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC): Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC) will expire soon!
2025-02-27 23:13:15.395 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Attempting to schedule notification for Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC) at: 1740632400000
2025-02-27 23:13:15.395 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1740632400000
2025-02-27 23:13:15.395 10185-10340 Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC) at 1740632400000
2025-02-27 23:13:15.395 10185-10340 EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  1740546000000
2025-02-27 23:13:15.395 10185-10340 DAYS NOTICE MILLIS:     sma...tory.smartinventorywithsearch  D  6912000000
2025-02-27 23:13:15.395 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1739941200000
2025-02-27 23:13:15.396 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Irish Penny Whistle - Key Of D | Book | Condition Good: Irish Penny Whistle - Key Of D | Book | Condition Good has expired!
2025-02-27 23:13:15.396 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Attempting to schedule notification for Irish Penny Whistle - Key Of D | Book | Condition Good at: 1739941200000
2025-02-27 23:13:15.396 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1739941200000
2025-02-27 23:13:15.396 10185-10340 Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Irish Penny Whistle - Key Of D | Book | Condition Good at 1739941200000
2025-02-27 23:13:15.396 10185-10340 EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  1747713600000
2025-02-27 23:13:15.396 10185-10340 DAYS NOTICE MILLIS:     sma...tory.smartinventorywithsearch  D  7776000000
2025-02-27 23:13:15.396 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1747108800000
2025-02-27 23:13:15.396 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  coke: coke will expire soon!
2025-02-27 23:13:15.396 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Attempting to schedule notification for coke at: 1747108800000
2025-02-27 23:13:15.396 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1747108800000
2025-02-27 23:13:15.397 10185-10340 Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for coke at 1747108800000
2025-02-27 23:13:15.397 10185-10340 EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  1741064400000
2025-02-27 23:13:15.397 10185-10340 DAYS NOTICE MILLIS:     sma...tory.smartinventorywithsearch  D  21600000000
2025-02-27 23:13:15.397 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1740459600000
2025-02-27 23:13:15.397 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Ratchet & Clank 2 for PlayStation 2: Ratchet & Clank 2 for PlayStation 2 will expire soon!
2025-02-27 23:13:15.397 10185-10340 Debug                   sma...tory.smartinventorywithsearch  D  Attempting to schedule notification for Ratchet & Clank 2 for PlayStation 2 at: 1740459600000
2025-02-27 23:13:15.397 10185-10340 NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  1740459600000
2025-02-27 23:13:15.398 10185-10340 Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Ratchet & Clank 2 for PlayStation 2 at 1740459600000
2025-02-27 23:13:15.408 10185-10185 WM-SystemJobService     sma...tory.smartinventorywithsearch  D  onStartJob for WorkGenerationalId(workSpecId=1b7fecec-6d33-4f51-b7d3-60c416f9d8a1, generation=0)
2025-02-27 23:13:15.412 10185-10226 WM-Processor            sma...tory.smartinventorywithsearch  D  Work WorkGenerationalId(workSpecId=1b7fecec-6d33-4f51-b7d3-60c416f9d8a1, generation=0) is already enqueued for processing
2025-02-27 23:13:15.413 10185-10225 WM-WorkerWrapper        sma...tory.smartinventorywithsearch  I  Worker result SUCCESS for Work [ id=1b7fecec-6d33-4f51-b7d3-60c416f9d8a1, tags={ smart.inventory.smartinventorywithsearch.ExpirationCheckWorker } ]
2025-02-27 23:13:15.413 10185-10185 WM-Processor            sma...tory.smartinventorywithsearch  D  Processor 1b7fecec-6d33-4f51-b7d3-60c416f9d8a1 executed; reschedule = false
2025-02-27 23:13:15.413 10185-10185 WM-SystemJobService     sma...tory.smartinventorywithsearch  D  1b7fecec-6d33-4f51-b7d3-60c416f9d8a1 executed on JobScheduler
2025-02-27 23:13:15.415 10185-10225 WM-GreedyScheduler      sma...tory.smartinventorywithsearch  D  Cancelling work ID 1b7fecec-6d33-4f51-b7d3-60c416f9d8a1
2025-02-27 23:13:15.420 10185-10225 WM-PackageManagerHelper sma...tory.smartinventorywithsearch  D  androidx.work.impl.background.systemalarm.RescheduleReceiver disabled
2025-02-27 23:13:15.903 10185-10185 VRI[EditActivity]       sma...tory.smartinventorywithsearch  D  visibilityChanged oldVisibility=true newVisibility=false
2025-02-27 23:13:15.915 10185-10215 HWUI                    sma...tory.smartinventorywithsearch  D  endAllActiveAnimators on 0xb4000071a6fe0980 (RippleDrawable) with handle 0xb4000071d6ea0980
2025-02-27 23:13:20.423 10185-10185 Notification            sma...tory.smartinventorywithsearch  D  Notification received!
2025-02-27 23:13:20.440 10185-10185 Notification            sma...tory.smartinventorywithsearch  D  Notification received!
2025-02-27 23:13:20.450 10185-10185 Notification            sma...tory.smartinventorywithsearch  D  Notification received!

From the logs, you see "daysNoticeMillis" is definitely correct as I have them set to 100 for Elder Scrolls (8640000000 millis), 80 for Irish Penny whistle (6912000000 millis), 90 for "coke" (7776000000 millis), and 250 for Ratchet and Clank 2 (21600000000 millis).

Expdate is also definitely correct, you see I have them set to March 6, 2025 for Elder Scrolls, February 26, 2025 for Irish Penny whistle, May 20, 2025 for "coke", and March 4, 2025 for Ratchet and Clank 2.

(double checked these by pasting straight from the converters)

So the problem is narrowed down to "notifytimemillis". For some reason it's returning past values:

February 27, 2025 (Elder Scrolls), February 19, 2025 (Irish Whistle), May 13, 2025 (randomly a decent value for coke? So that notification maybe is the one not firing and so that's all working as planned? May 13 is exactly 7 days out from it's expiration date on the 20th), and then again a poopy past value for Ratchet and Clank 2! (February 25, 2025).

So what do you guys think could account for this odd discrepency?

Here's my code in MainActivity:

private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            String channelId = "expiration_channel"; // Must match the ID in NotificationCompat.Builder
            String channelName = "Expiration Notifications";
            NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);

            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            if (notificationManager != null) {
                notificationManager.createNotificationChannel(channel);
            }
        }
    }


public void scheduleExpirationWorker() {
        OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(ExpirationCheckWorker.class)
                .build();

        WorkManager.getInstance(this).enqueueUniqueWork(
                "ExpirationCheckWorker",
                ExistingWorkPolicy.REPLACE,
                workRequest
        );
    }

@Override
    protected void onCreate(Bundle savedInstanceState)
    {
        Context context = this;
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...

        createNotificationChannel();
        scheduleExpirationWorker();
    ...
    }

...

// ----- Then I call that scheduleExpirationWorker() method whenever I save the lists
// ----- (which can be done with a button to save a new list, or by clicking a listview item to overwrite a list):

saveButton.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                String fileNameInput = saveEditText.getText().toString().trim();
                File listDirectory = new File(subDirectory, fileNameInput);

                if (!fileNameInput.isEmpty())
                {
                    if (!itemsList.isEmpty())
                    {
                        // ----------------- CREATE NEW LIST FOLDER ----------------- //

                        if (!listDirectory.exists())
                        {
                            listDirectory.mkdirs();     // Make the directory first since it doesn't exist yet

                            saveInventory(listDirectory);
                            makeToast("'" + listDirectory.getName() + "' saved!");
                        }
                        else
                        {
                            // -------- YES or NO warning dialog for overwriting lists -------- //

                            DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    switch (which){
                                        case DialogInterface.BUTTON_POSITIVE:
                                            //Yes button clicked

                                            // ----------------- SAVE INVENTORY TO SELECTED DIRECTORY ----------------- //
                                            saveInventory(listDirectory);                   // Save the data to existing directory

                                            makeToast("'" + listDirectory.getName() + "' overwritten!");
                                            //makeToast(listDirectory.getAbsolutePath());

                                            saveDialog.dismiss();         // Close save list" dialog after selecting a list to load

                                            break;

                                        case DialogInterface.BUTTON_NEGATIVE:
                                            //No button clicked
                                            break;
                                    }
                                }
                            };

                            AlertDialog.Builder builder = new AlertDialog.Builder(context);
                            builder.setMessage("This list already exists! Do you want to overwrite '" + fileNameInput + "'?").setPositiveButton("Yes", dialogClickListener)
                                    .setNegativeButton("No", dialogClickListener).show();
                        }

                        sdFilesArray = subDirectory.listFiles();         // Refresh File array of Sub Directory Folders

                        importFolders.clear();  // Clear the list for the loop...

                        // ---------- LOOP FOR POPULATING IMPORT LIST ---------- //
                        for (File f : sdFilesArray)
                        {
                            String filename = f.getName();
                            importFolders.add(filename);      // Add the folder name to the ArrayList
                        }

                        saveEditText.setText("");     // Reset the edit text to blank.

                        saveDialog.dismiss();

                        // Schedule notifications for expiring goods at the end of saving a list!
                        scheduleExpirationWorker();
                    }
                    else
                    {
                        makeToast("You can't save an empty list!");
                    }
                }
                else
                {
                    makeToast("You must give the list a name!");
                }
            }
        });
    ...
    saveListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id)
            {
                // ----- 2.9.4 FIX ----- //
                // Get folder name by looping through sdFilesArray
                // and checking for a match to the clicked item:

                String folderName = sdFilesArray[position].toString();

                for (File f : sdFilesArray)
                {
                    String listDir = folderName.substring(folderName.lastIndexOf("/") + 1);
                    if (folderName.equals(f.toString()))
                    {
                        // -------- YES or NO warning dialog for overwriting lists -------- //

                        DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                switch (which){
                                    case DialogInterface.BUTTON_POSITIVE:
                                        //Yes button clicked

                                        if (!itemsList.isEmpty())
                                        {
                                            // ----------------- SAVE INVENTORY TO SELECTED DIRECTORY ----------------- //
                                            File saveExistingList = new File(subDirectory, listDir);

                                            saveInventory(saveExistingList);

                                            makeToast("'" + listDir + "' overwritten!");

                                            saveDialog.dismiss();         // Close save list" dialog after selecting a list to load
                                        }
                                        else
                                        {
                                            makeToast("You can't save an empty list!");
                                        }

                                        break;

                                    case DialogInterface.BUTTON_NEGATIVE:
                                        //No button clicked
                                        break;
                                }
                            }
                        };

                        AlertDialog.Builder builder = new AlertDialog.Builder(context);
                        builder.setMessage("Are you sure you want to overwrite '" + listDir + "'?").setPositiveButton("Yes", dialogClickListener)
                                .setNegativeButton("No", dialogClickListener).show();
                    }
                }

                // Schedule notifications for expiring goods at the end of saving a list!
                scheduleExpirationWorker();
            }
        });

And here is the Notification Receiver class:

public class NotificationReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        Log.d("Notification", "Notification received!");
        String itemDataString = intent.getStringExtra("itemData");


        if (itemDataString == null) {
            Log.e("NotificationReceiver", "No valid item data received!");
            return;
        }

        try {
            JSONObject item = new JSONObject(itemDataString);
            String name = item.getString("name");

            String title = intent.getStringExtra("titleExtra");
            String message = intent.getStringExtra("messageExtra");

            // Build and send the notification
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            String channelId = "EXPIRATION_CHANNEL"; // Ensure this matches your NotificationChannel ID

            NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
                    .setSmallIcon(R.drawable.use_now_status) // Replace with your actual icon
                    .setContentTitle(title)
                    .setContentText(message)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setAutoCancel(true);

            if (notificationManager != null) {
                notificationManager.notify((int) System.currentTimeMillis(), builder.build());
            }

        } catch (JSONException e) {
            Log.e("NotificationReceiver", "Error parsing item data: " + e.getMessage());
        }
    }

}

Upvotes: -3

Views: 79

Answers (1)

Humble Roots
Humble Roots

Reputation: 9

Found the solution, thanks for all your help everybody! I will be upfront with you I am VERY BAD at using most debugging tools and stepping through the program with stops, but I recently became decent at using the Logcat, so the way I debugged this was by drawing it out on pencil and paper and visually mapping out the relationships of my variables with a Timeline Diagram!

I was going to write down the numbers and everything, but just in drawing this, it occurred to me that to my astonishment, I didn't have too many variables (which is definitely the way it seems, trust me I know) I was actually MISSING an important variable! The "notification threshold" as it were, and the respective "daysBeforeExpMillis" to calculate it. Folks were pointing out the inconsistency between days notice on one hand vs. days before exp on the other hand which I admit is extremely confusing cause it sounds like "days_before_exp" should be calculated via the current time. In reality there are two time ranges/windows one is the days notice window which is the bigger one that is fixed and determined by the user. The other window is the "days_before_exp" according-to-your-reminders window, which is not fixed, but adjusts itself based on how close to expiration we are.

So the solution was to put back in my days_until_exp-setting if statement WITH this new notification threshold and then my notifications started behaving correctly:

Log.d("ITEM NAME:", "ITEM NAME: " + name);

                String expDateString = item.getString("expDate");
                Date expDate = sdf.parse(expDateString); // Set to end of day (23:59:59)
                long expDateMillis = expDate.getTime();

                int daysNotice = item.getInt("daysNotice");
                long daysNoticeMillis = daysNotice * MILLIS_IN_A_DAY;

                long expirationThreshold = expDateMillis - daysNoticeMillis;

                long currentTimeMillis = System.currentTimeMillis();

                if (currentTimeMillis < (expDateMillis - (14 * MILLIS_IN_A_DAY))) {
                    // Far from expiring (more than DAYS_BEFORE_EXPIRY days away) – schedule for 14 days before expiry.
                    DAYS_BEFORE_EXPIRY = 14;
                } else if (currentTimeMillis < (expDateMillis - (7 * MILLIS_IN_A_DAY))) {
                    // Between 14 and 7 days before expiry – schedule for 7 days before expiry.
                    DAYS_BEFORE_EXPIRY = 7;
                } else if (currentTimeMillis < (expDateMillis - (3 * MILLIS_IN_A_DAY))) {
                    // Between 7 and 3 days before expiry – schedule for 3 days before expiry.
                    DAYS_BEFORE_EXPIRY = 3;
                } else if (currentTimeMillis < expirationThreshold) {
                    // Less than 3 days away but still before the user's notice threshold – schedule for 1 day before expiry.
                }

        ----->  long daysBeforeExpMillis = (MILLIS_IN_A_DAY * DAYS_BEFORE_EXPIRY);
                long notificationThreshold = expDateMillis - daysBeforeExpMillis;
                notificationThreshold = expDateMillis - daysBeforeExpMillis;  <-----

                Log.d("EXP DATE MILLIS:", "EXP DATE MILLIS: " + String.valueOf(expDateMillis));
                Log.d("DAYS NOTICE:", "DAYS NOTICE: " + String.valueOf(daysNotice));
                Log.d("DAYS NOTICE:", "DAYS NOTICE MILLIS: " + String.valueOf(daysNoticeMillis));
                Log.d("DAYS BEFORE EXP:", "DAYS BEFORE EXP: " + String.valueOf(DAYS_BEFORE_EXPIRY));
                Log.d("DAYS BEFORE EXP:", "DAYS BEFORE EXP MILLIS: " + String.valueOf(daysBeforeExpMillis));
                Log.d("NOTIFY TIME MILLIS:","NOTIFY TIME MILLIS: " + String.valueOf(notificationThreshold));

Here are my much cleaner logs that I was able to retrieve after debugging the issue:

2025-02-28 13:19:10.843  6370-6426  ITEM NAME:              sma...tory.smartinventorywithsearch  D  ITEM NAME: Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC)
2025-02-28 13:19:10.843  6370-6370  AutofillManager         sma...tory.smartinventorywithsearch  D  view not autofillable - not passing ime action check
2025-02-28 13:19:10.844  6370-6426  EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  EXP DATE MILLIS: 1741579200000
2025-02-28 13:19:10.845  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE: 100
2025-02-28 13:19:10.845  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE MILLIS: 8640000000
2025-02-28 13:19:10.845  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP: 7
2025-02-28 13:19:10.845  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP MILLIS: 604800000
2025-02-28 13:19:10.845  6370-6426  NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  NOTIFY TIME MILLIS: 1740974400000
2025-02-28 13:19:10.845  6370-6426  Debug                   sma...tory.smartinventorywithsearch  D  Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC) will expire soon!
2025-02-28 13:19:10.846  6370-6426  Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Elder Scrolls V: Skyrim (Xbox 360 / PS3 / PC) at 1740974400000
2025-02-28 13:19:10.846  6370-6426  ITEM NAME:              sma...tory.smartinventorywithsearch  D  ITEM NAME: Irish Penny Whistle - Key Of D | Book | Condition Good
2025-02-28 13:19:10.847  6370-6426  EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  EXP DATE MILLIS: 1740546000000
2025-02-28 13:19:10.847  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE: 80
2025-02-28 13:19:10.847  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE MILLIS: 6912000000
2025-02-28 13:19:10.847  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP: 7
2025-02-28 13:19:10.847  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP MILLIS: 604800000
2025-02-28 13:19:10.847  6370-6426  NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  NOTIFY TIME MILLIS: 1739941200000
2025-02-28 13:19:10.847  6370-6426  Debug                   sma...tory.smartinventorywithsearch  D  Irish Penny Whistle - Key Of D | Book | Condition Good has expired!
2025-02-28 13:19:10.848  6370-6426  Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Irish Penny Whistle - Key Of D | Book | Condition Good at 1739941200000
2025-02-28 13:19:10.848  6370-6426  ITEM NAME:              sma...tory.smartinventorywithsearch  D  ITEM NAME: coke
2025-02-28 13:19:10.849  6370-6426  EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  EXP DATE MILLIS: 1558324800000
2025-02-28 13:19:10.849  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE: 90
2025-02-28 13:19:10.849  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE MILLIS: 7776000000
2025-02-28 13:19:10.849  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP: 7
2025-02-28 13:19:10.849  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP MILLIS: 604800000
2025-02-28 13:19:10.849  6370-6426  NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  NOTIFY TIME MILLIS: 1557720000000
2025-02-28 13:19:10.849  6370-6426  Debug                   sma...tory.smartinventorywithsearch  D  coke has expired!
2025-02-28 13:19:10.850  6370-6426  Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for coke at 1557720000000
2025-02-28 13:19:10.854  6370-6426  ITEM NAME:              sma...tory.smartinventorywithsearch  D  ITEM NAME: Ratchet & Clank 2 for PlayStation 2
2025-02-28 13:19:10.855  6370-6426  EXP DATE MILLIS:        sma...tory.smartinventorywithsearch  D  EXP DATE MILLIS: 1741064400000
2025-02-28 13:19:10.855  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE: 250
2025-02-28 13:19:10.855  6370-6426  DAYS NOTICE:            sma...tory.smartinventorywithsearch  D  DAYS NOTICE MILLIS: 21600000000
2025-02-28 13:19:10.855  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP: 3
2025-02-28 13:19:10.855  6370-6426  DAYS BEFORE EXP:        sma...tory.smartinventorywithsearch  D  DAYS BEFORE EXP MILLIS: 259200000
2025-02-28 13:19:10.855  6370-6426  NOTIFY TIME MILLIS:     sma...tory.smartinventorywithsearch  D  NOTIFY TIME MILLIS: 1740805200000
2025-02-28 13:19:10.855  6370-6426  Debug                   sma...tory.smartinventorywithsearch  D  Ratchet & Clank 2 for PlayStation 2 will expire soon!
2025-02-28 13:19:10.858  6370-6426  Alarm                   sma...tory.smartinventorywithsearch  D  Scheduled notification for Ratchet & Clank 2 for PlayStation 2 at 1740805200000

...

2025-02-28 13:19:15.860  6370-6370  Notification            sma...tory.smartinventorywithsearch  D  Notification received! // Irish Penny Whistle (expired)
2025-02-28 13:19:15.883  6370-6370  Notification            sma...tory.smartinventorywithsearch  D  Notification received! // Coke (expired)

Long story short, trying to use the expiration threshold based on the user input was putting my notifyTimeMillis value in the past exactly like Computable said, which is obvious when you're looking at the timeline lol. Now the notifications only fire prematurely if an item is already expired -which happens to be exactly the behavior I wanted anyway so it's a wrap! 😁

Again I really appreciate your folks' help, have a good one everybody!

Upvotes: 1

Related Questions