how we tried to keep track of the actual time in android

Marty, is the episode still available?
Marty, is the episode still available?

This article will focus on how we, at PREMIER, tried to keep the uploaded content up to date and what came of it. Perhaps the publication will be useful to those who decide to monitor the device time translations in the absence of a connection.


By agreement with the copyright holder, all downloaded films and series can be available to the user only for 14 days. Therefore, we must restrict the user’s access to view downloads after this period. The expiration time for a series is calculated at the time it is downloaded and verified against the time on the device.

But a situation may arise when the user deliberately turns off the Internet and, removing the last opportunity to synchronize time with the server, sets the time on the device back, thereby extending the ability to view content for himself. In order to avoid problems with copyright holders and not to limit users who, for some reason, moved the time forward, reducing the content availability time for themselves, it was decided to try to track the real remaining time for downloads.

Rolling up your sleeves

Initially, the idea was to use the library AndroidTrueTime, but due to the fact that it uses NTP (Network Time Protocol), and we needed to work offline, it had to be thrown away. There was also an idea to use the system time from Linux (Hardware Time), which would not change after the time was translated, but to use it, the user must have BusyBox or something similar.

We decided to set the time changes on our own according to the system notification coming from the BroadcastReceiver. Since Android does not send any information about how much the time on the device has changed, but simply sends an event notification, there is a need to calculate the difference between the old and new device time.

To do this, we used a bunch of system variables:

System.currentTimeMillis() // возвращает текущее время время в миллисекундах
SystemClock.elapsedRealtime() // возвращает время от старта системы в миллисекундах

The difference between them:

private fun currentTimestamp(): Long {
    return System.currentTimeMillis() - SystemClock.elapsedRealtime()

This made it possible to obtain an immutable timestamp, which would differ only when the time on the device changes and when the device is rebooted, but more on that later.

Thus, in order to find out how much time has changed, we find the difference:

val timeChangeInMillis = savedTimestamp - currentTimestamp

savedTimestamp set by the method currentTimestamp() when the application is first launched and serves as the base reference point for time difference calculations.

The change in time is stored in a variable timeChangingDelta.

The output of the current time began to look like this:

val timeChangingDelta = preferences.getLong(KEY_LAST_DIFFERENCE, NO_TIME)
val timestamp = System.currentTimeMillis() + timeChangingDelta

Insurmountable difficulties

But what if the device was rebooted, because at that moment elapsedRealtime resets to 0, breaking our calculations? That’s right, additionally save it to a variable elapsingTimeChangingDelta. Rebooting the device can be determined by the sign that the saved value will become greater than the real one (because the real one will be reset) or as an analogue, you can use Settings.Global.BOOT_COUNTwhich returns the number of device reboots.

val wasDeviceRebooted = Settings.Global.BOOT_COUNT > savedBootCount

If the device was rebooted, then calculate the change elapsedRealtime and summarize with previous changes:

val elapsedTimeDelta = preferences.getLong(KEY_ELAPSED_TIME, NO_TIME) - SystemClock.elapsedRealtime()
preferences.edit {
	val previousSum = getLong(KEY_ELAPSING_DELTA_SUM, NO_TIME)
    putLong(KEY_ELAPSING_DELTA_SUM, previousSum + elapsedTimeDelta)

The method for getting the time began to look like this:

fun timestamp(): Long {
        when {
            hasTimestamps() -> updateTimestamp() 
            else -> initializeTimestamps()

        val timeChangingDelta = preferences.getLong(KEY_LAST_DIFFERENCE, NO_TIME)
        val elapsingTimeChangingDelta = preferences.getLong(KEY_ELAPSING_DELTA_SUM, NO_TIME)
        return System.currentTimeMillis() + (timeChangingDelta + elapsingTimeChangingDelta)

Stored value elapsedRealtime adds up to our timeChangingDeltaand thus we find the time shift.

Another obstacle turned out to be that the time translation event arrives with a delay or, depending on the OS of the device, does not arrive at all when the application is closed. All calculations needed to be performed already at the time of displaying the content, which could happen with a long delay after the time transfer or reboot.

Reeling the fishing rods

It became clear that in the current form, without additional synchronization, it would not be possible to set the current time.

As a result, the approach did not cope with the task in full and had to be abandoned. The user will still be able to bypass the protection by rebooting and changing the time. But the solution may be useful for someone in case of time changes within the same reboot cycle of the device.

The problem had to be solved in a different way, keeping the last opening time of the series in lastOpenTimestamp and if it is found that the time of the device is less than the saved time, block access to the series. (currentTimestamp < lastOpenTimestamp) . This did not solve all the problems, but allowed to protect the content.

Similar Posts

Leave a Reply