Tempo: A new Android library to get the time from multiple sources

Getting the correct, current time on Android isn’t a trivial task. The System.currentTimeMillis() method can’t be trusted; both the user and the phone network can change it, making time jump backwards or forwards unpredictably.

Tempo is a new library that tackles this issue by relying on multiple sources for the current time. At this stage, Tempo supports the following sources: the SNTP protocol and the time from the GPS; but Tempo is extensible, you can write your own source for it 🤗

Tempo is also built to be easy to use. No need to deal with async code to get the current time, just call a single method: Tempo.now()

The following sections will expand on how, and why, Tempo works.

System.currentTimeMillis() […] can be set by the user or the phone network […] so the time may jump backwards or forwards unpredictably.
https://developer.android.com/reference/android/os/SystemClock.html. July, 2017.

Getting the current time fast

Acquiring the current time from a time source like SNTP or the GPS takes a considerable amount of time. Not only that, Android will even crash your app if you try to do a SNTP request from the app’s main thread.

That’s why Tempo will get the current time from the time sources asynchronously, but then, offers a fast, synchronous interface to the user:

val timeInMs = Tempo.now()

This is possible because Tempo only needs to request the current time once per time source. Subsequent queries only need to compute how much time has passed since the last request, as in the following formula:

currentTime = requestTime + (currentUptime-requestUptime)

where, requestTime is the Unix epoch time returned by the time source, currentUptime is the current up-time (milliseconds since boot) of the device, and requestUptime was the up-time of the device when the request was made. Both requestTime and requestUptime are cached and reused later to compute the currentTime . currentUptime is always computed on-the-fly.

Note how this formula doesn’t depend on the unreliable System.currentTimeMillis(). Instead, it uses the SystemClock.elapsedRealTime() to get the device’s up-time, a much safer alternative because neither the user nor the network can change it.

And that’s how Tempo can work synchronously 👌

Implementing a custom time source

Another fundamental part of Tempo is its ability to handle multiple sources of time. Each source implements the TimeSource interface:

The Single<Long> is from RxJava and it allows you to write complex async code to get the current time, if needed. This Single is never directly accessible to the user; it’s all managed by Tempo, the one deciding when and how this Single will execute. Tempo handles the cases where the Single timeouts or gives an error. Tempo also coordinates the Single’s execution with the others time sources. And, in case all time sources fail, Tempo will adopt a strategy to keep retrying. See the GitHub project for more detail on this.

Process death

The downside of using a SNTP or the GPS time sources is that they require internet access or the GPS; things that may not always be available. Worse, the app can even be killed by the heartless Android OS.

Thankfully Tempo will handle a process death by saving its cache in the app’s shared preferences storage. So, even on the event of the app being destroyed, it can recover itself gracefully without a need for network or GPS.

However, since Tempo depends on the device up-time to properly compute the current time, it’ll require a full sync after a reboot. This is already handled by the library.

Scheduling syncs

We saw how Tempo can cache the results to return the current time synchronously. But, a device clock may not be extremely accurate, and if enough time pass-by, it’ll get out of sync. In order to prevent this from happening, Tempo comes with a scheduler to periodically sync all the time sources. See the provided GitHub project to learn how to add it to your project.

Fallback strategy

It’s possible that a device may not have internet access, nor GPS, and just recently rebooted–invalidating Tempo’s cache. In that situation, your only choice is to use the System.currentTimeMillis() as a fallback strategy.

Just keep in mind that a malicious user who knows how the app works can easily, and purposefully exploit it to inject whatever time he wants into the app. Without a server, there’s not much you can do 😔

Conclusions

Tempo faces the problem of getting the correct current time and adds an extra layer of reliability. It’s not perfect and will not work in every situation, but it’s designed to function in most use cases. And you can always extend Tempo’s functionalities to include your own source of time.

Tempo is built with an easy to use design. Give it a try 😉