Skip to main content
  1. Blogs/

Why your android app's timing features are probably broken

·3 mins

If you’ve ever built an Android app with scheduling features, user cooldowns, or time based restrictions, there’s a good chance your app has a hidden vulnerability. The problem? Most developers rely on System.currentTimeMillis() for timing logic, which can be easily manipulated by users.

The Problem with System Time #

Here’s the issue: System.currentTimeMillis() returns whatever time the user has set on their device. If they change their clock, your app’s timing logic breaks.

Real-world example: You build a fitness app that prevents users from logging the same workout twice within 24 hours. A user could simply change their device clock forward by a day, bypassing your restriction entirely.

This isn’t just a theoretical problem it happens in production apps for example:

  • Scheduled notifications fire at the wrong time
  • Cooldown periods get bypassed
  • Time sensitive features behave unpredictably

Google’s Solution #

Google offers the TrustedTime API through Play Services as a solution. This API provides a time value derived from Google’s servers.

So, how does TrustedTime achieve this? It periodically checks in with Google’s time servers for an accurate time reference. The clever part is what happens between these syncs: it relies on the device’s monotonic clock. This internal clock simply measures elapsed time, like a reliable stopwatch, and isn’t affected if the user changes the main system clock. By using the last server time and adding the time elapsed on the monotonic clock, TrustedTime provides a timestamp resistant to user manipulation. It continues to work offline after an initial sync and even provides an estimate of its potential drift (uncertainty).

Think of it as having a reliable watch that occasionally checks with an atomic clock to stay accurate.

Getting Started #

1. Add the Dependency #

Add this to your app’s build.gradle.kts:

dependencies {
    implementation("com.google.android.gms:play-services-base:18.4.0")
}

2. Create a Time Service #

Instead of calling System.currentTimeMillis() directly throughout your app, create a service that provides trusted time:

class TimeService {

    fun getCurrentTimeMillis(): Long {
        // Try to get trusted time first
        val trustedTime = TrustedTime.getInstance()

        return if (trustedTime.hasCache()) {
            trustedTime.currentTimeMillis()
        } else {
            // If not available add a fallback or throw an exception unavailable
        }
    }

    fun isTimeReliable(): Boolean {
        return TrustedTime.getInstance().hasCache()
    }
}

3. Use It in Your App #

Before (unreliable):

val fireAt = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24)

After (reliable):

val now = timeService.getCurrentTimeMillis()
val fireAt = now + TimeUnit.HOURS.toMillis(24)

Important Considerations #

Device Compatibility #

TrustedTime requires Google Play Services. For devices without it (some Huawei devices, custom ROMs), you’ll need a fallback. TrustedTime also needs internet connectivity for its first sync after device boot.

Further more there is security limitations in using TrustedTime it can potentially be bypassed on rooted devices with modified Play Services. For most apps, this is an acceptable limitation.

Conclusion #

If your app depends on accurate timing, System.currentTimeMillis() isn’t reliable enough. TrustedTime provides a robust alternative that protects your app’s logic from user manipulation.

The implementation is straightforward and the reliability improvement is significant. For any production app with time sensitive features, it’s worth the small integration effort.

Resources #