gms_trusted_time 1.1.0 copy "gms_trusted_time: ^1.1.0" to clipboard
gms_trusted_time: ^1.1.0 copied to clipboard

PlatformAndroid

A Flutter plugin for Android that provides tamper-resistant, accurate UTC time via the Google Play Services TrustedTime API. Works offline after first sync.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:gms_trusted_time/gms_trusted_time.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GMS Trusted Time Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1A73E8)),
        useMaterial3: true,
      ),
      home: const TrustedTimeScreen(),
    );
  }
}

class TrustedTimeScreen extends StatefulWidget {
  const TrustedTimeScreen({super.key});

  @override
  State<TrustedTimeScreen> createState() => _TrustedTimeScreenState();
}

class _TrustedTimeScreenState extends State<TrustedTimeScreen> {
  final _plugin = GmsTrustedTime();

  bool _loading = true;
  TrustedTimeResult? _trustedResult;
  int? _systemMillis;
  String? _error;

  @override
  void initState() {
    super.initState();
    _fetchTime();
  }

  Future<void> _fetchTime() async {
    setState(() {
      _loading = true;
      _error = null;
    });

    try {
      // Use the rich method to get both epoch and accuracy
      final result = await _plugin.getTrustedTimeWithAccuracy();
      final systemMillis = DateTime.now().millisecondsSinceEpoch;
      setState(() {
        _trustedResult = result;
        _systemMillis = systemMillis;
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _loading = false;
      });
    }
  }

  String _formatMillis(int? millis) {
    if (millis == null) return 'N/A';
    return DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true)
        .toIso8601String();
  }

  @override
  Widget build(BuildContext context) {
    final drift = (_trustedResult != null && _systemMillis != null)
        ? _trustedResult!.epochMillis - _systemMillis!
        : null;

    return Scaffold(
      appBar: AppBar(
        title: const Text('GMS Trusted Time'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: _loading
            ? const Center(child: CircularProgressIndicator())
            : _error != null
                ? Center(
                    child: Text('Error: $_error',
                        style: TextStyle(
                            color: Theme.of(context).colorScheme.error)),
                  )
                : Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      _TimeCard(
                        label: 'Trusted UTC Time',
                        value: _formatMillis(_trustedResult?.epochMillis),
                        subtitle: _trustedResult != null
                            ? 'Source: Google Play Services TrustedTime API'
                            : 'Unavailable: Device has not connected to internet\nsince last reboot, or GMS is unavailable.',
                        isAvailable: _trustedResult != null,
                      ),
                      const SizedBox(height: 12),
                      // Accuracy card — only shown when result is available
                      if (_trustedResult != null)
                        _AccuracyCard(result: _trustedResult!),
                      const SizedBox(height: 12),
                      _TimeCard(
                        label: 'System Clock (DateTime.now)',
                        value: _formatMillis(_systemMillis),
                        subtitle: 'Source: Device system clock (user-modifiable)',
                        isAvailable: true,
                      ),
                      if (drift != null) ...[
                        const SizedBox(height: 12),
                        _DriftCard(driftMs: drift, context: context),
                      ],
                      const Spacer(),
                      SizedBox(
                        width: double.infinity,
                        child: FilledButton.icon(
                          onPressed: _fetchTime,
                          icon: const Icon(Icons.refresh),
                          label: const Text('Refresh'),
                        ),
                      ),
                    ],
                  ),
      ),
    );
  }
}

class _AccuracyCard extends StatelessWidget {
  final TrustedTimeResult result;
  const _AccuracyCard({required this.result});

  @override
  Widget build(BuildContext context) {
    final hasUncertainty = result.uncertaintyNanos != null;
    final uncertaintyText = hasUncertainty
        ? '±${result.uncertaintyMs!.toStringAsFixed(1)} ms'
        : 'Not available (Android 7.1 or below)';

    return Card(
      color: Theme.of(context).colorScheme.surfaceContainerHighest,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.speed,
                    size: 18,
                    color: Theme.of(context).colorScheme.primary),
                const SizedBox(width: 8),
                Text('Time Accuracy',
                    style: Theme.of(context).textTheme.labelLarge),
              ],
            ),
            const SizedBox(height: 8),
            Text(
              uncertaintyText,
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.bold,
                    color: hasUncertainty
                        ? Theme.of(context).colorScheme.primary
                        : Theme.of(context).colorScheme.onSurfaceVariant,
                  ),
            ),
            if (hasUncertainty)
              Text(
                'Raw: ${result.uncertaintyNanos} ns',
                style: Theme.of(context).textTheme.bodySmall?.copyWith(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
              ),
          ],
        ),
      ),
    );
  }
}

class _DriftCard extends StatelessWidget {
  final int driftMs;
  final BuildContext context;
  const _DriftCard({required this.driftMs, required this.context});

  @override
  Widget build(BuildContext context) {
    final isLarge = driftMs.abs() > 5000;
    return Card(
      color: isLarge
          ? Theme.of(context).colorScheme.errorContainer
          : Theme.of(context).colorScheme.secondaryContainer,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Clock Drift',
                style: Theme.of(context).textTheme.labelLarge),
            const SizedBox(height: 4),
            Text(
              '${driftMs > 0 ? '+' : ''}${driftMs}ms',
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            Text(
              isLarge
                  ? '⚠ Significant drift — system clock may be manipulated.'
                  : '✓ System clock is within normal range.',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }
}

class _TimeCard extends StatelessWidget {
  final String label;
  final String value;
  final String subtitle;
  final bool isAvailable;

  const _TimeCard({
    required this.label,
    required this.value,
    required this.subtitle,
    required this.isAvailable,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  isAvailable ? Icons.check_circle : Icons.warning_amber,
                  color: isAvailable ? Colors.green : Theme.of(context).colorScheme.error,
                  size: 18,
                ),
                const SizedBox(width: 8),
                Text(label, style: Theme.of(context).textTheme.labelLarge),
              ],
            ),
            const SizedBox(height: 8),
            Text(value,
                style: Theme.of(context)
                    .textTheme
                    .bodyMedium
                    ?.copyWith(fontWeight: FontWeight.bold)),
            const SizedBox(height: 4),
            Text(subtitle,
                style: Theme.of(context).textTheme.bodySmall?.copyWith(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    )),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
24
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Android that provides tamper-resistant, accurate UTC time via the Google Play Services TrustedTime API. Works offline after first sync.

Repository (GitHub)
View/report issues

Topics

#time #android #google-play-services #security #ntp

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on gms_trusted_time

Packages that implement gms_trusted_time