Keeping independent time on an android device

    Hello!

    One fine day the manager comes to me and says: “Can we prevent the user from changing the time on the phone?”. And of course, my answer was no, but this did not solve the problem. It was necessary to look for a way out of the situation.
    The criteria for the decision were as follows:
    • should work without frequent synchronization with the server, for example, it is enough to take the time once a month.
    • must be resistant to moving time backward / forward / change time zone
    • work when the device reboots / unexpectedly terminates / pulls out the battery
    • Do not deviate from the reference time by too large values, in my case it was 5 minutes.
    • if you still managed to cheat, then track this moment


    We sat down, thought, and another acceptable option was found - to conduct our own with blackjack and ... time independent of the device.



    disclaimer
    This solution does not guarantee accuracy to milliseconds. The error is 1-4 minutes.
    It is not protected from hacking (bypass) by especially advanced users. For that matter, everything breaks. Designed for the average layman.


    So, let's begin.
    First, create a class that will be responsible for storing time. As a place, I chose SharedPreferences.
    Because trivial things are done here, then I’ll hide it in a spoiler so that my eyes do not callous.
    class SettingsMaster
    class SettingsMaster
    {
        private static final String FILE_SETTINGS = "prop";
        private static final String LOCAL_TIME = "LOCAL_TIME";
        private static final String SYSTEM_TIME = "SYSTEM_TIME";
        private static final String FLASH_BACK = "FLASH_BACK";
        private static SharedPreferences getPreference(final Context context)
        {
            return context.getSharedPreferences(FILE_SETTINGS, Context.MODE_PRIVATE);
        }
        private static SharedPreferences.Editor getEditor(final Context context)
        {
            return getPreference(context).edit(); 
        }
        public static void setTime(final Context context, final long mls)
        {
            getEditor(context).putLong(LOCAL_TIME, mls).apply();
        }
        public static long getTime(final Context context)
        {
            return getPreference(context).getLong(LOCAL_TIME, 0);
        }
        public static void setSystemTime(final Context context, final long mls)
        {
            getEditor(context).putLong(SYSTEM_TIME, mls).apply();
        }
        public static long getSystemTime(final Context context)
        {
            return getPreference(context).getLong(SYSTEM_TIME, 0);
        }
        public static void setFlashBack(final Context context, final boolean isFlashback)
        {
            getEditor(context).putBoolean(FLASH_BACK, isFlashback).apply();
        }
        public static boolean isFlashBack(final Context context)
        {
            return getPreference(context).getBoolean(FLASH_BACK, false);
        }
    }
    



    Next up is the class that provides the main api. He will save and give time, he will start a timer, which will update the time.
    Everything is also quite commonplace. The only thing that is interesting here: when setting the server time, we must first stop the timer, save the new server time, and then restart it.
    class IndependentTimeHelper
    public class IndependentTimeHelper
    {
        public static void setServerTime(final Context context, final long serverTime)
        {
            stopTimer(context);
            SettingsMaster.setTime(context, serverTime);
            SettingsMaster.setFlashBack(context, false);
            SettingsMaster.setSystemTime(context,System.currentTimeMillis());
            startTimer(context);
        }
        static void startTimer(final Context context)
        {
            final Intent intent = new Intent(context, TimeReceiver.class);
            intent.setAction(TimeReceiver.ACTION_TO_UPDATE_TIME);
            if (PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE) == null)
            {
                final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
                final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + TimeReceiver.TIME_PERIOD, TimeReceiver.TIME_PERIOD, pendingIntent);
            }
        }
        static void stopTimer(final Context context)
        {
            final Intent intent = new Intent(context, TimeReceiver.class);
            intent.setAction(TimeReceiver.ACTION_TO_UPDATE_TIME);
            final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE);
            if (pendingIntent != null)
            {
                final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                alarmManager.cancel(pendingIntent);
                pendingIntent.cancel();
            }
        }
        public static long getTime(final Context context)
        {
            if (SettingsMaster.isFlashBack(context))
                return -1;
            return SettingsMaster.getTime(context);
        }
    }
    



    Let's move on to the interesting. All the main logic fell on the receiver.
    The receiver is subscribed to three events: start at boot, start at shutdown, start at time update.

    What should happen when the time is updated is clear, time must be incremented.
        private void incrementTimeAndSaveSystemTime(final Context context)
        {
            final long localTime = SettingsMaster.getTime(context) + TIME_PERIOD;
            SettingsMaster.setTime(context, localTime);
            SettingsMaster.setSystemTime(context, System.currentTimeMillis());
        }
    

    The value for TIME_PERIOD was selected 30 seconds. And no, it does not affect the battery. The application in which it works is always installed on my device, and everything is cool.

    The next step is to remember the system time so that we can know the approximate time that the device was turned off.
    if (action.equals(Intent.ACTION_SHUTDOWN))
           SettingsMaster.setSystemTime(context, System.currentTimeMillis());
    


    And, finally, the most important thing is calculating the time that the device was in the off state.
    First, get the last saved system time
    final long systemTime = SettingsMaster.getSystemTime(context);
    

    and calculate the time off
    final long offTime = System.currentTimeMillis() - systemTime;
    

    if it is less than or equal to zero, then we stumbled upon the translation of time back. We were not particularly interested in moving forward, and it is very difficult to track it.
    if (offTime <= 0)
        SettingsMaster.setFlashBack(context, true);
    


    add it to the current one and start the timer
    final long localTime = SettingsMaster.getTime(context);
    final long newLocalTime = localTime + offTime;
    SettingsMaster.setTime(context, newLocalTime);
    IndependentTimeHelper.startTimer(context);
    

    full receiver code
    public class TimeReceiver extends BroadcastReceiver
    {
        public static final String ACTION_TO_UPDATE_TIME = "com.useit.independenttime.ACTION_TO_UPDATE_TIME";
        public static final long TIME_PERIOD = 30 * 1000;
        @Override
        public void onReceive(Context context, Intent intent)
        {
            if (SettingsMaster.getTime(context) <= 0)
            {
                IndependentTimeHelper.stopTimer(context);
                return;
            }
            final String action = intent.getAction();
            if (action.equals(Intent.ACTION_BOOT_COMPLETED))
                startReceiverAfterBootComplete(context);
            if (action.equals(Intent.ACTION_SHUTDOWN))
                SettingsMaster.setSystemTime(context, System.currentTimeMillis());
            if (action.equals(ACTION_TO_UPDATE_TIME))
                incrementTimeAndSaveSystemTime(context);
        }
        private void startReceiverAfterBootComplete(final Context context)
        {
            final long systemTime = SettingsMaster.getSystemTime(context);
            if (systemTime > 0)
            {
                final long offTime = System.currentTimeMillis() - systemTime;
                if (offTime <= 0)
                    SettingsMaster.setFlashBack(context, true);
                final long localTime = SettingsMaster.getTime(context);
                final long newLocalTime = localTime + offTime;
                SettingsMaster.setTime(context, newLocalTime);
                IndependentTimeHelper.startTimer(context);
            }
        }
        private void incrementTimeAndSaveSystemTime(final Context context)
        {
            final long localTime = SettingsMaster.getTime(context) + TIME_PERIOD;
            SettingsMaster.setTime(context, localTime);
            SettingsMaster.setSystemTime(context, System.currentTimeMillis());
        }
    }
    



    That's all. Done.
    Do not forget about adding permission to the manifest


    Sources and example The

    result was a working system for maintaining time on the device. Yes, it is not perfect, but it solves the task well.

    PS. the manager is satisfied.

    Also popular now: