Skip to main content
  1. Blogs/

Stop Using System.currentTimeMillis() for Anything That Matters

·4 mins

Using System.currentTimeMillis() for features like scheduling events or enforcing cooldowns introduces a significant vulnerability. Users can easily change the system clock. This manipulation, whether accidental or intentional (e.g. to bypass time restrictions), can break your application’s logic.

Let’s say for example scheduling a local notification exactly 24 hours after a user action. If the user manually sets their device clock forward by a day, the notification might trigger immediately. Conversely, setting the clock back could delay or prevent it indefinitely. This isn’t a theoretical problem; it occurs in real-world usage. The system clock should not be trusted for operations requiring accurate timekeeping.

Use TrustedTime for Reliable Time #

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).

This makes TrustedTime suitable for scheduling, enforcing delays, or any feature dependent on real-world time progression.

Adding the Dependency #

Before using TrustedTime in your code, you need to make sure your app includes the necessary Google Play Services library. Add the following dependency to your build.gradle.kts file, inside the dependencies block:

dependencies {
    // ... other dependencies
    implementation("com.google.android.gms:play-services-base:18.4.0") // Use the latest version
}

Implementation Example #

Replace direct calls to System.currentTimeMillis() with a source that provides the trusted time.

Instead of:

// Unreliable: Depends on user-changeable system clock
val fireAt = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24)

Use:

// Assume timeService provides TrustedTime or a fallback
val now = timeService.getCurrentTimeMillis()
// Reliable: Calculation based on trusted time source
val fireAt = now + TimeUnit.HOURS.toMillis(24)

We recommend abstracting the TrustedTime logic behind a service (e.g., TimeService) that can return the trusted time when available, potentially falling back to the system clock if necessary. This service can be injected using dependency injection (like Koin or Hilt) and initialized early in the application lifecycle.

Example using WorkManager:

class MyViewModel(private val timeService: TimeService) : ViewModel() {

    fun scheduleNotification() {
        // 1. Get the current reliable time
        val now = timeService.getCurrentTimeMillis()
        // 2. Calculate the target fire time based on reliable time
        val fireAt = now + TimeUnit.HOURS.toMillis(24)

        // 3. Schedule the work
        scheduleNotificationAt(fireAt)
    }

    private fun scheduleNotificationAt(fireAt: Long) {
        // Calculate delay: target time (reliable) minus system's current time
        // WorkManager needs delay relative to System.currentTimeMillis() baseline
        val delayMillis = Math.max(0, fireAt - System.currentTimeMillis())

        val notificationWorkRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
            .setInitialDelay(delayMillis, TimeUnit.MILLISECONDS)
            .build()

        WorkManager.getInstance(applicationContext).enqueue(notificationWorkRequest)
    }
}

Important Considerations #

  • Play Services Dependency: TrustedTime requires Google Play Services. It will not function on devices lacking them (e.g., some Huawei devices, devices with certain custom ROMs). Your application needs a fallback mechanism for these cases.
  • Initial Sync: The API needs an internet connection to perform its first sync after the device boots. If the device is offline immediately after restarting, TrustedTime might not return a value initially. Ensure your application handles this state.
  • Doesn’t Change System Clock: TrustedTime provides your app with a separate, reliable time source. It does not modify the actual device system clock.
  • Security Limits: While effective against casual clock manipulation by users, TrustedTime can potentially be circumvented on rooted or significantly modified devices where the underlying Play Services or OS components could be tampered with. For most applications, this is an acceptable limitation.

Conclusion #

If your application relies on accurate timing for its features, relying on System.currentTimeMillis() is risky. Google’s TrustedTime API offers a much more robust alternative with a manageable integration effort. Adopt it to prevent unexpected behavior caused by system clock changes.

References #