
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:
We sat down, thought, and another acceptable option was found - to conduct our ownwith blackjack and ... time independent of the device.
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.
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.
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.
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.
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
and calculate the time off
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.
add it to the current one and start the timer
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.
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
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.
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.