I've got a couple of users in my app reporting a weird behavior of a time reset/bug, i was baffled by it and after testing and collecting logs - i figured that my timer, if started before midnight, resets to 20+ hours instantly, i've can't figure out why or how to prevent this.
The project is open source and anyone is welcome to help me with this, here are the links:
GitHub
Info about the app/project:
Select apps, select desired lock-down time, lock apps.
My Timer Class ( Full )
Time/Date related snippet
public class Timer_Service extends Service {
public static final long NOTIFY_INTERVAL = 1000;
public static String str_receiver = "com.nephi.getoffyourphone.receiver";
public String str_testing;
Calendar calendar;
SimpleDateFormat simpleDateFormat;
String strDate;
Date date_current, date_diff;
//Root Detector
RootBeer rootbeer;
//Con Manager
WifiManager wifiManager;
//DH Helper
DB_Helper db;
Intent intent;
Intent lockIntent;
Intent Shame;
//Shame Int Counter
private Handler mHandler = new Handler();
private Timer mTimer = null;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
//Lock Screen launch
lockIntent = new Intent(this, locked.class);
lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
calendar = Calendar.getInstance();
simpleDateFormat = new SimpleDateFormat("H:M:ss");
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 5, NOTIFY_INTERVAL);
intent = new Intent(str_receiver);
//Root Detector
rootbeer = new RootBeer(this);
//Wifi Manager
wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
//DB
db = new DB_Helper(this);
}
public String twoDatesBetweenTime() {
try {
date_current = simpleDateFormat.parse(strDate);
} catch (Exception e) {
}
try {
// Gets the first timestamp when the lockdown started
date_diff = simpleDateFormat.parse(db.get_Data(1));
} catch (Exception e) {
}
try {
long diff = date_current.getTime() - date_diff.getTime();
int int_hours = Integer.valueOf(db.get_Hours(1));
long int_timer;
if (int_hours > 10) {
int_timer = TimeUnit.MINUTES.toMillis(int_hours);
} else {
int_timer = TimeUnit.HOURS.toMillis(int_hours);
}
long long_hours = int_timer - diff;
long diffSeconds2 = long_hours / 1000 % 60;
long diffMinutes2 = long_hours / (60 * 1000) % 60;
long diffHours2 = long_hours / (60 * 60 * 1000) % 24;
if (long_hours >= 0) {
str_testing = String.format("%d:%d:%02d", diffHours2, diffMinutes2, diffSeconds2);
Log.e("TIME", str_testing);
} else {
stopService(new Intent(getApplicationContext(), Timer_Service.class));
mTimer.cancel();
}
Any suggestions ?
EDIT:
Timer works fine, if someone sets the lockdown for example, 30 minutes, it will countdown 30 minutes and unlock when it's done.
Let's say someone started the lockdown at 21:30:00, timer will work just fine and end at 10:00:00.
However, if someone started the lockdown at 23:55:00 for 30 minutes, the "Time left to unlock" will jump to 21 hours, instead of 30 minutes. This only happens when a new day starts/timer starts before midnight.
The problem occurs because you have the time stored in "H:m:ss" format, but you are ignoring the date aspect. Using System.currentTimeMillis() gives you the milliseconds after Jan 1 1970. So this includes the date aspect. Taking the difference of the two values will give you the milliseconds expired, which you can check against any time interval for the elapsed time.
Do this instead:
When saving the time for the timer should start use the millisecond instead of H:mm:ss :
long millisNow = System.currentTimeMillis();
save millisNow to the database.
And save the time that should elapse:
int minutes = 30;
long millisElapse = 30 * 60 * 1000;
save this to the database (or which ever time interval you need)
now get the difference
long diff = timeNow - timeDatabase;
if(diff > millisElapse){
//end session
}
BTW: The format you posted "H:M:ss". The capital "M" stands for month(!) not minutes.
Take a look at the documentation on the "Date and Time Patterns" SimpleDateFormat
Related
It wont get the minutes. i need to return minutes.
How to return sum of minutes while iterating over Localtime in Java?
public String userLunchHoursSum(String username) {
List<WorkHour> workHours = workHourRepository.findWorkHoursByUsername(username);
System.out.println(Arrays.toString(workHours.toArray()));
long diff = 0;
LocalTime lunchTime;
long minutes = 0;
LocalTime plusMinutes = null;
for (WorkHour workHour : workHours) {
lunchTime = workHour.getLunch_time().toLocalTime(); //00:30:00
plusMinutes = lunchTime.plusMinutes(lunchTime.getMinute());
}
if(workHours.size()!= 0) {
return Long.toString(plusMinutes.getMinute());
}
return "00:00";
}
getLunch_time returns java.sql.Time.
As mentioned, you should be storing duration instead of localtime. If this is something you have no control over, consider migrating the database or creating a intermediate parsing function. Example code that I have not run, because I don't know what is in WorkHour.
// leave the string formatting to other functions
public long userLunchHoursSum(String username) {
List<WorkHour> workHours = workHourRepository.findWorkHoursByUsername(username);
Duration totalDuration = Duration.ZERO;
for (WorkHour workHour : workHours) {
// save your time in the appropriate format beforehand
// do not use local time to store duration.
Duration lunchTime = Duration.between(LocalTime.MIDNIGHT, workHour.getLunch_time().toLocalTime()); //00:30:00
totalDuration = totalDuration.plus(lunchTime);
}
return totalDuration.toMinutes();
}
I use UsageStats feature of Android, but the smallest interval is DAILY INTERVAL.
long time = System.currentTimeMillis();
List<UsageStats> appList = manager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - DAY_IN_MILLI_SECONDS, time);
How can I get UsageStats in an hourly interval?
All credit goes to this answer. I have learned from that one.
How can we collect app usage data for customized time range (e.g. for per 1 hour)?
We have to call queryEvents(long begin_time, long end_time) method as it will provide us all data starting from begin_time to end_time. It give us each app data through foreground and background events instead of total spent time like queryUsageStats() method. So, using foreground and background events time stamp, we can count the number of times an app has been launched and also can find out the usage duration for each app.
Implementation to Collect Last 1 Hour App Usage Data
At first, add the following line in the AndroidManifest.xml file and also request user to get permission of usage access.
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
Add the following lines inside any method
long hour_in_mil = 1000*60*60; // In Milliseconds
long end_time = System.currentTimeMillis();
long start_time = end_time - hour_in_mil;
Then, call the method getUsageStatistics()
getUsageStatistics(start_time, end_time);
getUsageStatistics methiod
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
void getUsageStatistics(long start_time, long end_time) {
UsageEvents.Event currentEvent;
// List<UsageEvents.Event> allEvents = new ArrayList<>();
HashMap<String, AppUsageInfo> map = new HashMap<>();
HashMap<String, List<UsageEvents.Event>> sameEvents = new HashMap<>();
UsageStatsManager mUsageStatsManager = (UsageStatsManager)
context.getSystemService(Context.USAGE_STATS_SERVICE);
if (mUsageStatsManager != null) {
// Get all apps data from starting time to end time
UsageEvents usageEvents = mUsageStatsManager.queryEvents(start_time, end_time);
// Put these data into the map
while (usageEvents.hasNextEvent()) {
currentEvent = new UsageEvents.Event();
usageEvents.getNextEvent(currentEvent);
if (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED ||
currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
// allEvents.add(currentEvent);
String key = currentEvent.getPackageName();
if (map.get(key) == null) {
map.put(key, new AppUsageInfo(key));
sameEvents.put(key,new ArrayList<UsageEvents.Event>());
}
sameEvents.get(key).add(currentEvent);
}
}
// Traverse through each app data which is grouped together and count launch, calculate duration
for (Map.Entry<String,List<UsageEvents.Event>> entry : sameEvents.entrySet()) {
int totalEvents = entry.getValue().size();
if (totalEvents > 1) {
for (int i = 0; i < totalEvents - 1; i++) {
UsageEvents.Event E0 = entry.getValue().get(i);
UsageEvents.Event E1 = entry.getValue().get(i + 1);
if (E1.getEventType() == 1 || E0.getEventType() == 1) {
map.get(E1.getPackageName()).launchCount++;
}
if (E0.getEventType() == 1 && E1.getEventType() == 2) {
long diff = E1.getTimeStamp() - E0.getTimeStamp();
map.get(E0.getPackageName()).timeInForeground += diff;
}
}
}
// If First eventtype is ACTIVITY_PAUSED then added the difference of start_time and Event occuring time because the application is already running.
if (entry.getValue().get(0).getEventType() == 2) {
long diff = entry.getValue().get(0).getTimeStamp() - start_time;
map.get(entry.getValue().get(0).getPackageName()).timeInForeground += diff;
}
// If Last eventtype is ACTIVITY_RESUMED then added the difference of end_time and Event occuring time because the application is still running .
if (entry.getValue().get(totalEvents - 1).getEventType() == 1) {
long diff = end_time - entry.getValue().get(totalEvents - 1).getTimeStamp();
map.get(entry.getValue().get(totalEvents - 1).getPackageName()).timeInForeground += diff;
}
}
smallInfoList = new ArrayList<>(map.values());
// Concatenating data to show in a text view. You may do according to your requirement
for (AppUsageInfo appUsageInfo : smallInfoList)
{
// Do according to your requirement
strMsg = strMsg.concat(appUsageInfo.packageName + " : " + appUsageInfo.launchCount + "\n\n");
}
TextView tvMsg = findViewById(R.id.MA_TvMsg);
tvMsg.setText(strMsg);
} else {
Toast.makeText(context, "Sorry...", Toast.LENGTH_SHORT).show();
}
}
AppUsageInfo.class
import android.graphics.drawable.Drawable;
class AppUsageInfo {
Drawable appIcon; // You may add get this usage data also, if you wish.
String appName, packageName;
long timeInForeground;
int launchCount;
AppUsageInfo(String pName) {
this.packageName=pName;
}
}
How can I customize these codes to collect per 1 hour data?
As you want to get per hour data, please change the end_time and start_time value for every hour data. For instance: If I would try to collect past per hour data (for past 2 hour data). I would do the following thing.
long end_time = System.currentTimeMillis();
long start_time = end_time - (1000*60*60);
getUsageStatistics(start_time, end_time);
end_time = start_time;
start_time = start_time - hour_in_mil;
getUsageStatistics(start_time, end_time);
However, you may use a Handler to skip repeatedly writing start_time and end_time to change value of these variables. Each time data will be collected for one hour, a task will be completed and after automatically changing the values of the variables, you will again call the getUsageStatistics method.
Note: Maybe, you will not be able to retrieve data for more than past 7.5 days as events are only kept by the system for a few days.
Calendar cal = (Calendar) Calendar.getInstance().clone();
//I used this and it worked, only for 7 days and a half ago
if (daysAgo == 0) {
//Today - I only count from 00h00m00s today to present
end = cal.getTimeInMillis();
start = LocalDate.now().toDateTimeAtStartOfDay().toInstant().getMillis();
} else {
long todayStartOfDayTimeStamp = LocalDate.now().toDateTimeAtStartOfDay().toInstant().getMillis();
if (mDaysAgo == -6) {
//6 days ago, only get events in time -7 days to -7.5 days
cal.setTimeInMillis(System.currentTimeMillis());
cal.add(Calendar.DATE, daysAgo + 1);
end = cal .getTimeInMillis();
start = end - 43200000;
} else {
//get events from 00h00m00s to 23h59m59s
//Current calendar point to 0h0m today
cal.setTimeInMillis(todayStartOfDayTimeStamp);
cal.add(Calendar.DATE, daysAgo + 1);
end = calendar.getTimeInMillis();
cal.add(Calendar.DATE, -1);
start = calendar.getTimeInMillis();
}
}
I don't think it's possible, even if you ask for data in the middle of an interval, it looks like the data is stored in buckets and the minimum bucket is a day.
In UsageStatsManager documentation, it says:
A request for data in the middle of a time interval will include that interval.
Also, INTERVAL_BEST is not a real interval, it just selects one of the available intervals for the given time range. In
UsageStatsManager.java source code, it says:
/**
* The number of available intervals. Does not include {#link #INTERVAL_BEST}, since it
* is a pseudo interval (it actually selects a real interval).
* {#hide}
*/
public static final int INTERVAL_COUNT = 4;
Yes, Android is providing minimum INTERVAL_DAILY. But for the best result, you can use INTERVAL_BEST. Android is giving the best interval timer for the given time range in queryUsageStats(int, long, long).
Happy coding...
I have java.util.Timer class and TimerTask to schedule, I want to do the task every day at 2AM.
My code:
public class AutomaticPuller {
final private Timer t = new Timer();
public AutomaticPuller() {
Calendar today = Calendar.getInstance();
today.set(Calendar.HOUR_OF_DAY, 2);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
long cooldown = today.getTimeInMillis();
if (today.getTime().before(new Date(System.currentTimeMillis()))) {
cooldown += 24L*60L*60L*1000L;
}
System.out.println("Task will run at: " + new Date(cooldown));
TimerTask tt = new TimerTask() {
public void run() {
try {
updateAll();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.schedule(tt, cooldown, 24L*60L*60L*1000L);
}
}
I see the output from println (Task will run at:) but the task that should run at 2AM is never executed, why ? I dont get it I have never met such a problem. No errors in output log.
Because you are using todays time in milliseconds as the delay in milliseconds before task is to be executed. That means the task will execute in about 47 years.
Fixed by adding this line:
cooldown = cooldown - System.currentTimeMillis();
Basically I just forgot to remove the current millis from the cooldown.
I have to create a scheduler which runs at a particular day of week. For example my scheduler should run on every Monday at 11:50 PM. Please help me through the task.
PS: I went through these links How to schedule task daily + onStart() in Play 2.0.4? suggests using a cronJob expression to calculate next execution time. Is there a way to do using akka by default i.e. without a cronJob expression?
schedule(initialDelay: Duration, frequency: Duration, receiver: ActorRef, message: Any)
You just need to calculate the initialDelay on the scale (minutes, hours, days) that you want. In your case, you have to find out the time until the next Monday. That's not an issue related with Akka, just plain Java:
//In minutes
private long timeToNextMonday(){
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR, 23);
now.set(Calendar.MINUTE, 50);
int weekday = now.get(Calendar.DAY_OF_WEEK);
System.out.println(now.getTime());
if (weekday != Calendar.MONDAY){
// calculate how much to add
// the 2 is the difference between Saturday and Monday
int days = (Calendar.SATURDAY - weekday + 2) % 7;
now.add(Calendar.DAY_OF_YEAR, days);
}
Date date = now.getTime();
return (now.getTime().getTime() - System.currentTimeMillis())/(1000*60);
}
And then the schedule call itself is pretty straightforward:
Akka.system().scheduler().schedule(
Duration.create(timeToNextMonday, TimeUnit.MINUTES),
Duration.create(7, TimeUnit.DAYS),
actor, actorMessage,
Akka.system().dispatcher(), null);
public void onStart(Application application) {
try{
Duration.create(timeToNextMonday(), TimeUnit.MILLISECONDS),
Duration.create(7, TimeUnit.DAYS),
new Runnable() {
#Override
public void run() {
JPA.withTransaction(new F.Callback0() {
#Override
public void invoke() throws Throwable {
System.out.println("Printing time : " + new Date());
}
});
}
},
Akka.system().dispatcher());
}
catch (Throwable t){
HashMap<String,String> params = new HashMap<>();
Logger.error("{}:params:{}", "error while starting cron for Historical TW questions", params, t);
}
super.onStart(application);
}
//In minutes
private long timeToNextMonday(){
Calendar now = Calendar.getInstance();
while (now.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
now.add(Calendar.DATE, 1);
}
now.set(Calendar.HOUR,11);
now.set(Calendar.AM_PM,Calendar.PM);
now.set(Calendar.MINUTE,50);
now.set(Calendar.SECOND,00);
now.set(Calendar.MILLISECOND,00);
return now.getTime().getTime() - Calendar.getInstance().getTime().getTime();
}
Task - Turn a bulb on and off at a specified time during a day. I need to know how to fix my code as per the information given below. I also need to know if I am using the timer class correctly, that is, is my code design correct ? The code may work but it could be bad design which will cause problems later. I don't want that to happen.
Output is (This is not the output i really wanted :( ) -
This is the main program
Current time is - xxx
Future time is - xxx+5sec
Future time is - xxx+10sec
Main program ends
Bulb B1 is OFF
Desired output -
This is the main program
Current time is - xxx
Future time is - xxx+5sec
Future time is - xxx+10sec
Bulb B1 is ON //first on
Bulb B1 is OFF //then off
Main program ends//This should always be in the end.
How do I fix the code below to get what I want ?
Bulb Class
class Bulb {
private boolean state = false;//On or off
private String name;
Bulb(String name){
this.name = name;
}
public void setState(boolean state){
this.state = state;
if(this.state == true){
System.out.println("Bulb " + name + " is ON");
}else{
System.out.println("Bulb " + name + " is OFF");
}
}
public boolean getState(){
return this.state;
}
}
BulbJob class which is a TimerTask
import java.util.*;
class BulbJob extends TimerTask{
private Bulb bulbToHandle;
private boolean setBulbStateEqualTo;
BulbJob(Bulb toHandle){
this.bulbToHandle = toHandle;
}
//NOTE: Must be called before run(), otherwise default value is used
public void setBulbStateEqualTo(boolean setBulbStateEqualTo){
this.setBulbStateEqualTo = setBulbStateEqualTo;
}
//NOTE: call run() only before calling above method
public void run(){
this.bulbToHandle.setState(setBulbStateEqualTo);//Set on or off
}
}
BulbScheduler class - this schedules when the bulb is turned on or off.
import java.util.*;
#SuppressWarnings( "deprecation" )
class BulbScheduler {
public static void main(String args[]) throws InterruptedException{
System.out.println("This is the main program");
Timer time = new Timer();
Bulb b1 = new Bulb("B1");
BulbJob bj = new BulbJob(b1);
bj.setBulbStateEqualTo(true);//Task - Turn bulb on at time = afterCurrent
Date current = new Date();//Get current time and execute job ten seconds after this time
Date afterCurrent = (Date) current.clone();
System.out.println("Current time is - " + current);
int currentSecs = current.getSeconds();
int offset = 5;//number of seconds
afterCurrent.setSeconds(currentSecs + offset);
System.out.println("Future time is - " + afterCurrent);
time.schedule(bj, afterCurrent);//Schedule job "bj" at time = afterCurrent
//Now turn the bulb off at new time = newest afterTime
afterCurrent.setSeconds(currentSecs + 2 * offset);
System.out.println("Future time is - " + afterCurrent);
bj.setBulbStateEqualTo(false);//Task - Now turn the bulb off at time = afterCurrent
System.out.println("Main program ends");
}
}
This section:
time.schedule(bj, afterCurrent);//Schedule job "bj" at time = afterCurrent
//Now turn the bulb off at new time = newest afterTime
afterCurrent.setSeconds(currentSecs + 2 * offset);
only schedules one task. If you need to schedule it twice, do so explicitly:
time.schedule(bj, afterCurrent);//Schedule job "bj" at time = afterCurrent
//Now turn the bulb off at new time = newest afterTime
afterCurrent.setSeconds(currentSecs + 2 * offset);
time.schedule(bj, afterCurrent);//Schedule job "bj" at time = afterCurrent
Also. this line:
bj.setBulbStateEqualTo(false);
is executed in the main thread, so it will before both tasks. You should schedule that statement to run between the two tasks.
Code is fixed, but this version cannot exit main in the end -
import java.util.*;
#SuppressWarnings( "deprecation" )
class BulbScheduler {
public static void main(String args[]) throws InterruptedException{
System.out.println("This is the main program");
Timer timeOn = new Timer();
Timer timeOff = new Timer();
Bulb b1 = new Bulb("B1");
BulbJob bjOn = new BulbJob(b1);
BulbJob bjOff = new BulbJob(b1);
bjOn.setBulbStateEqualTo(true);//Task - Turn bulb on
bjOff.setBulbStateEqualTo(false);//Task - Then turn the bulb off later
Date current = new Date();//Get current time and execute job ten seconds after this time
Date afterCurrent = (Date) current.clone();
System.out.println("Current time is - " + current);
int currentSecs = current.getSeconds();
int offset = 3;//number of seconds
afterCurrent.setSeconds(currentSecs + offset);
System.out.println("Future time is - " + afterCurrent);
timeOn.schedule(bjOn, afterCurrent);//Schedule job "bj" at time = afterCurrent
//Now turn the bulb off at new time = latest afterCurrent
afterCurrent.setSeconds(currentSecs + 2 * offset);
System.out.println("Future time is - " + afterCurrent);
timeOff.schedule(bjOff, afterCurrent);
System.out.println("Main program ends");
}
}
you are not setting the time correctly. Need to use GreogarianCalendar.
java.util.Date is used but cannot use its setSeconds Read the Javadoc its pretty good and will help a lot. public void setSeconds(int seconds)
Deprecated. As of JDK version 1.1, replaced by Calendar.set(Calendar.SECOND, int seconds).
Sets the seconds of this Date to the specified value. This Date object is modified so that it represents a point in time within the specified second of the minute, with the year, month, date, hour, and minute the same as before, as interpreted in the local time zone.
You need to use java.util.GregorianCalendar # add(Calendar.SECOND, howManySeconds)
then use getDate() to get the Date object and send it to the Timer.
calling setSecond on a date wont change the other fields. see the java doc of Calendar.add and roll. http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Calendar.html and see the rules in the class inro.
Can also use the timer object's schedule(TimerTask task, long delay)
Schedules the specified task for execution after the specified delay (milliseconds).
Modified code -
import java.util.*;
class BulbScheduler {
private static java.text.SimpleDateFormat sdf1 = new java.text.SimpleDateFormat ("yy MM dd HH mm ss");
//helper
static String formatDate(Date d){
return sdf1.format(d);
}
public static void main(String args[]) throws InterruptedException{
System.out.println("This is the main method");
java.util.GregorianCalendar cal = new java.util.GregorianCalendar();
Bulb b1 = new Bulb("bulb 1", false);
Bulb b2 = new Bulb("bulb 2", false);
System.out.println("Time now " + formatDate(cal.getTime()));
Timer timer = new Timer("bulbs");
BulbJob b1On = new BulbJob(b1, true);
BulbJob b1Off = new BulbJob(b1, false);
BulbJob b2On = new BulbJob(b2, true);
BulbJob b2Off = new BulbJob(b2, false);
timer.schedule(b1On, 3 * 1000);//after 3 seconds
timer.schedule(b2On, 7 * 1000);//after 4 seconds
timer.schedule(b1Off, 6 * 1000);//after 6 seconds; before b2 on
b1On = new BulbJob(b1, true);
timer.schedule(b1On, 9 * 1000);
//if you want main to wait need to add code here to make it wait,
// but even if does the JVM wont exit. Its just a method. The JVM exits when all non daemon threads are done
// or System.exit is called
System.out.println("This is the main method ending; but other threads might be running ...");
//main thread JVM waits for all other non dameons to end
}
}
Changed BulbJob
import java.util.*;
class BulbJob extends TimerTask{
private Bulb bulbToHandle;
private boolean bulbNewState;//dont start propert names with set
//why a seperate property when we need to set the new state everytime and cannot reuse jobs?
BulbJob(Bulb toHandle, boolean newState){
this.bulbToHandle = toHandle;
bulbNewState= newState;
}
public void run(){
this.bulbToHandle.setState(bulbNewState);//Set on or off
}
}
class Bulb ...
public void setState(boolean state){
this.state = state;
System.out.println("Bulb " + name + " is " + (state ? "on" : "off") + " at " + BulbScheduler.formatDate(new java.util.Date()));//if okay too
}