5

Background: I have an Android application running on Android 7.1. A reccurring task is set using AlarmManage to perform a task every day at a specific time. code is like below:

AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
long startInMs = getComing9AM();
Intent intent = ...; //an intent to be run when alarm time is up.
PendingIntent ScheduledIntent = PendingIntent.getBroadcast(context, id, intent, 0);
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, startInMs, ScheduledIntent);


private long getComing9AM(){
    long now = System.currentTimeMillis();
    long today = getToday9AM();
    return (now < today? today:getTomorrow9AM());
}

private long getToday9AM(){
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 9);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    return calendar.getTimeInMillis();
}

private long getTomorrow9AM(){
    return getToday9AM() + 7 * 24 * 3600 * 1000;
}
  1. The android device is not a mobile phone, but a small box. Under normal operation, only power cable is plugged with WIFI connectivity.

Problem Description:

  1. The scheduled task works pretty well every day. After three weeks or more, the task no longer running.
  2. All other functionality still working, including MQTT client communication, IO operations.

Analysis result:

  1. After checking the pattern of the problem, it is found that around 25 days from last boot up the task will not work. Being a math-geek who is sensitive to number, "25 days" = 2,160,000 second = 2,160,000,000 ms. And this value is very closed to 2^31-1 (Integer.MAX_VALUE), which is equivalent to 24.85 days.

  2. HDMI cable is plugged to the device, the screen shows no input.

  3. Even a new scheduled task is assigned after 25days (thru HTTPS), the task will not be executed.

  4. After a USB device (i tried a mouse) is plugged to the device, Android is wake up (the screen has a display). And the Android has been awakened, the task can be run normally every day until the next 25 days.

  5. using ADB to check the Android setting, I can only find one parameter value is met with Integer.MAX_VALUE. That is (adb shell dumpsys power):

    mMaximumScreenOffTimeoutFromDeviceAdmin=2147483647 (enforced=false) Screen off timeout: 2147483647 ms

  6. Based on finding #5, I have tried to set the screen off timeout to 60000 trying to reproduce the problem. But the task can still be run as usual.

  7. It should not be related to Doze mode as the device is always charged and no battery with it.

  8. (New finding) The value of "mWakefulness" in dumpsys power is Asleep after 24.85days. Before 24.85days this value should be Awake.

  9. (New finding) Given that wWakefulness=Asleep arelay, the "mLastSleepTime"=now - (startupTimestamp + 24.85day).

Seek for advice:

  1. I would like to have some direction to further investigate the problem.

  2. I would like to know if there is a way to simulate the waking up event within the application? (as a workaround before finding the root cause).

Kuma C.
  • 66
  • 3
  • You're going to have to provide how you do "some calculations" to obtain `long startInMs = ...; //some calcuationg` – Martin Marconcini Aug 10 '20 at 14:08
  • I have put the relevant code accordingly – Kuma C. Aug 11 '20 at 02:07
  • Very interesting, I wouldn't use those methods to calculate "tomorrow at 9am" but if it works for a few days, then I don't know; are you (in the actual Intent) doing something that may be leaking or accumulating something that overflows after 25 days? It's the only thing I can think of, short of an AlarmManager bug (wouldn't surprise me) Any chance to test this w/ a different Android version (just to see if it has anything to do) – Martin Marconcini Aug 11 '20 at 10:05
  • For what is worth, timestamps (and ms since Epoch) are normally represented as `Long` not Int, which gives you (off of the top of my head) `0x7fffffffffffffffL` (I may have missed an F there) but think of a 9 + about 18 zeros :D which is many many years. The integer.MAX overflow should give you "seconds" from Jan 1st 1970 all the way to the [year 2038](https://en.wikipedia.org/wiki/Year_2038_problem) so I'm still not convinced this is an Integer overflow (but I am also not sure it's otherwise). – Martin Marconcini Aug 11 '20 at 10:09

2 Answers2

0

I would like to have some direction to further investigate the problem.

I would find ways to unit test your code to ensure it works fine. You can fire intents regularly and try that. Either in a Unit test or via adb. You mentioned "via https" but perhaps you need to go straight to the "metal" and trigger the methods yourself to test.

I would like to know if there is a way to simulate the waking up event within the application? (as a workaround before finding the root cause).

Your device is not a phone, so I'm not sure if it's capable of receiving Firebase Push Notifications (or any form of), since you'd need Google Play services for this, and that comes with another bag of things, but I would try to manually control these events (externally) to make sure your code is not causing issues. Does making a "beep" (or printing to logcat) every day at 9am still work?

If AlarmManager somehow breaks periodically after NN days, then something is either really broken (it would be strange to be honest, 7.1 is rather old and has been in use for a long time, someone would have said something about this, I hope(?)), or your code is misbehaving.

Start isolating things, can you create a simple app that ALL it does is print a log ever day at 9:30 am. Does it still work?

Or would work manager help you here or would there be other dependencies that you cannot use/have? There are tradeoffs of course, for WorkManager is not the same as AlarmManager and is way more constrained.

Even a new scheduled task is assigned after 25days (thru HTTPS), the task will not be executed.

This is also puzzling, since it appears the AlarmManager stopped working and receiving work altogether. Does the job work if you broadcast the intent manually via adb at that point? Do you have more than one device of these? does it happen with all?

This is a very interesting problem, I wish I had more concrete stuff, but very few people will know the inner workings of the AlarmManager in relation to all the new ways google is trying to "save battery" on phones.

setExactAndAllowWhenIdle is described as:

Unlike other alarms, the system is free to reschedule this type of alarm to happen out of order with any other alarms, even those from the same app. This will clearly happen when the device is idle (since this alarm can go off while idle, when any other alarms from the app will be held until later), but may also happen even when not idle. Note that the OS will allow itself more flexibility for scheduling these alarms than regular exact alarms, since the application has opted into this behavior. When the device is idle it may take even more liberties with scheduling in order to optimize for battery life.

Could the OS allowing itself to be flexible in some form and ignoring your alarm? hmmm not sure.

Final comment, have you taken a look at the AlarmManager's source code? Perhaps there's a hint in there.

In the end, this long post (that should have been a comment) doesn't really give you a solution, just an open place for me to dump ideas. :) I'll update if need; please let us know what you find :)

As a side-comment: I'd test if you can use Gradle 4.x and have access to Java 8 APIs for java.time (much better than calendar and even util.Date), or try to leverage the (now archived/deprecated but still usable for some time until Gradle 4.x is "streamlined") ThreeTenABP abstraction from Wharthon to use the same Java8 APIs (the migration from ThreeTen to java.time is really a 10 minute find/replace to change the imports).

In short, you can replace all that calendar nonsense with

val tomorrowAt9Am = ZonedDateTime.now().plusDays(1).withHour(9).withMinute(0).withSecond(0).toEpochSecond()

EDIT: the last two points you found out (mWakefulness) are very interesting, i'll keep looking. In the meantime, I found a few instances of said variable in the Android codebase. The most interesting one is this one from the AttentionDetector (what a name); feel free to browse all the results

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
0

I have studied the source code of PowerManagerService.java in Android 7.1.2. I could see the "sleep mode" due to "idle screen timeout" can be skipped by setting "mStayOn" to a non-zero value.

So, I tried to set this value to 1 (skip if AC power is connected) on a device that is going to "asleep" in 3 days. Tada, the scheduled intent can now be executed upon the task time up. However, I still could not find the reason why time-up task not being executed when Android is in asleep mode. Maybe just understand that as a rule and that's it.

====

For item 6 I have mentioned, the value could not be modified as the PowerManagerService.java used in my embedded system has already been modified since day one I got the source code. The modification is to return MAX integer value all the time regardless of the parameters I have assigned thru adb.

Kuma C.
  • 66
  • 3