📜 ⬆️ ⬇️

Keeping independent time on android device

Hello!

One day, the manager comes to me and says: “Can we forbid the user to change the time on the phone?”. And of course, my answer was no, but that did not solve the problem. It was necessary to look for a way out of the situation.
The criteria for the solution were as follows:


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


disclaimer
This solution does not guarantee accuracy up to milliseconds. The tolerance is 1-4 minutes.
Not protected from hacking (bypass) by very advanced users. For that matter, everything breaks down. Calculated on the average man in the street.


So, let's begin.
To begin, create a class that will be responsible for storing time. I chose SharedPreferences as the place.
Since banal things are done here, then I will hide it in the spoiler so as not to be an eyesore.
SettingsMaster class
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 is the class that provides the basic api. He will save and give time, he will start the timer, which will update the time.
It's all pretty commonplace too. The only thing that is interesting here: when installing 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); } } 



We turn to the interesting. All the main logic came to the receiver.
The receiver is subscribed to three events: start at boot, start at shutdown, start at update time.

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 app in which it works is always installed on my device, and everything is cool.

The next step is to memorize 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 the calculation of the time that the device was in the off state.
First we 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 transfer 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. Is done.
Do not forget about adding permissions to the manifest
 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 


Sources and Example

The result was a working time management system on the device. Yes, it is not perfect, but the problem is solved well.

Ps. the manager is pleased.

Source: https://habr.com/ru/post/245477/


All Articles