Counting time in background service - java

I've made a running app and for that, I've made a Service class to update user location (removed unimportant code):
public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener, ILocationConstants {
private static final String TAG = LocationService.class.getSimpleName();
protected GoogleApiClient mGoogleApiClient;
protected LocationRequest mLocationRequest;
private LocationData data;
#Override
public void onCreate() {
super.onCreate();
data = new LocationData();
startForeground();
}
private void startForeground() {
//BUIDLING A NOTIFICATION
startForeground(101, notification);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
data = Parcels.unwrap(intent.getParcelableExtra(LOCATION_MESSAGE));
buildGoogleApiClient();
mGoogleApiClient.connect();
if (mGoogleApiClient.isConnected()) {
startLocationUpdates();
}
return START_STICKY;
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
createLocationRequest();
}
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
protected void startLocationUpdates() {
try {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
} catch (SecurityException ex) {
} catch (Exception e) {
}
}
private void sendUpdates() {
data.millisecondTime = SystemClock.uptimeMillis() - data.startTime;
data.updateTime = data.timeBuff + data.millisecondTime;
data.seconds = (int) (data.updateTime / 1000);
data.minutes = data.seconds / 60;
data.hours = data.minutes / 60;
data.minutes = data.minutes % 60;
data.seconds = data.seconds % 60;
data.milliSeconds = (int) (data.updateTime % 1000);
Intent locationIntent = new Intent();
locationIntent.setAction(LOACTION_ACTION);
locationIntent.putExtra(LOCATION_MESSAGE, Parcels.wrap(data));
LocalBroadcastManager.getInstance(this).sendBroadcast(locationIntent);
}
protected void stopLocationUpdates() {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
#Override
public void onDestroy() {
stopLocationUpdates();
mGoogleApiClient.disconnect();
super.onDestroy();
}
#Override
public void onConnected(Bundle connectionHint) throws SecurityException {
Log.i(TAG, "Connected to GoogleApiClient");
if (data.mLastLocation == null) {
data.mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
sendUpdates();
}
startLocationUpdates();
}
#Override
public void onLocationChanged(Location location) {
sendLocationChanged(location);
Log.d(TAG, "onLocationChanged: " + location.getLatitude() + ", " + location.getLongitude());
sendUpdates();
}
public void sendLocationChanged(Location location) {
//SEND DATA TO THE SERVER
}
#Override
public void onConnectionSuspended(int cause) {
mGoogleApiClient.connect();
}
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
}
#Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
After each location update I'm also calculating elapsed time (I have to do this by adding, because user may pause the activity). The problem is when I'm running the app in my office time is exact (for example 35 minutes), but when I'm riding a bicycle time is too short (shows 15mins when actually about 30 minutes passed). For the time perpsective the only difference is that it's called more often (in office gps refreshes only each 10-20 seconds, but outside may be even each 2 seconds). The route and distance are fine - service is not killed or something like that. I'm also updating time in activity, but replacing it on each update with the broadcasted (by the service) time, but even removing this didn't fix the issue. Why is this happening?

As mentioned in the SystemClock documentation,
uptimeMillis() is counted in milliseconds since the system was booted.
This clock stops when the system enters deep sleep (CPU off, display
dark, device waiting for external input)
So that may be the reason why you get different times. On the other hand, there are elapsedRealtime() and elapsedRealtimeNanos() methods, which
return the time since the system was booted, and include deep sleep.
This clock is guaranteed to be monotonic, and continues to tick even
when the CPU is in power saving modes, so is the recommend basis for
general purpose interval timing
Try using one of them.

I would suggest using the time you get with each location update in onLocationChanged() and passing it to your sendUpdates() method
void onLocationChanged(Location location){
long currentTime=location.getTime(); // unix time in milliseconds
.......
sendUpdates(currentTime);
}
then in sendUpdates(long currentTime) do the time calculations. If I understand correctly you are interested only in elapsed time since your app started. In that case I guess you can simplify your data object by having just data.startTime, data.seconds, data.minutes and data.hours for the elapsed time. You can obtain the start time from the location of the first fix after the app start (utilizing flag or similar to detect the first fix).
Then in sendUpdates(long currentTime) I would calculate the sec, min, hour of the elapsed time like this:
int elapsedSeconds=int((currentTime-data.StartTime)/1000);
data.hours=int(elapsedSeconds/3600);
data.minutes=int(elapsedSeconds/60)-60*data.hours;
data.seconds=elapsedSeconds-60*(data.minutes+60*data.hours);

Related

How to update activity every 2 minutes using broadcast receiver in Android?

I am using broadcastreceiver to update the Activity every minute, however, I want to change it to every 2 minutes. How can I achieve that?
Below is the code for my function -
private void startMinuteUpdated() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_TIME_TICK);
minuteUpdateReceiver= new BroadcastReceiver() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onReceive(Context context, Intent intent) {
lastUpdatedTimeTextDeparture.setText(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm")));
}
};
registerReceiver(minuteUpdateReceiver, intentFilter);
}
#Override
protected void onResume() {
super.onResume();
startMinuteUpdated();
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(minuteUpdateReceiver);
}
You can alter your current implementation to read the minutes from current time and if its remainder by 2 is 0 then do your stuff, or else return. Doing this will allow you to update when the minutes are in even value (i.e. running every second minute).
if (Calendar.getInstance().get(Calendar.MINUTE) % 2 != 0){
// don't proceed further
return;
}
// update the activity

How can i keep the job service running when the app is closed from recent task list by user

I am using Job Scheduler API in my app to schedule a job for me after specific time interval. It runs fine when the app is running. But whenever the user closes the app or clears it from the recent task list the app stops and the scheduled job never executes afterwards until you open the app and it is rescheduled again from the time it is opened.
Now i want someone to help me to keep the jobs on executing even if the app is closed or cleared from the recent task list.
If there is any alternative solution please tell me.
i am looking for the solution from the past 3 days. Tried everything said by developers on StackOverFlow and other sites and none of them worked for me.
This is where is schedule the job!
ComponentName componentName = new
ComponentName(getActivity().getBaseContext(),WallpaperJobService.class);
JobInfo jobInfo = new JobInfo.Builder(777,componentName)
.setRequiresCharging(sharedPreferences.getBoolean("Charging",false))
.setRequiredNetworkType(sharedPreferences.getBoolean("Wifi",false) ?
JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(sharedPreferences.getInt("Duration",15) * 60 *
1000)
.setPersisted(true)
.build();
JobScheduler scheduler = (JobScheduler)
getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
My Job Service Class:
public class WallpaperJobService extends JobService {
private boolean jobCancelled;
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;
#Override
public boolean onStartJob(JobParameters params) {
Log.i("WallpaperJobService", "Job started!");
changeWallpaper(params);
return true;
}
private void changeWallpaper(final JobParameters params) {
final ArrayList<Image> images = (ArrayList<Image>)
MainActivity.favoritesRoomDatabase.roomDao().getAllFavoriteWallpapers();
sharedPreferences = getSharedPreferences("GridSize", MODE_PRIVATE);
editor = sharedPreferences.edit();
if (images != null && images.size() != 0) {
if (sharedPreferences.getInt("Index", 0) == images.size()) {
editor.putInt("Index", 0);
editor.commit();
}
Picasso.get().load(Constants.domain +
images.get(sharedPreferences.getInt("Index", 0)).getImage_url()).into(new
Target() {
#Override
public void onBitmapLoaded(final Bitmap bitmap,
Picasso.LoadedFrom from) {
new Thread(new Runnable() {
#Override
public void run() {
if (jobCancelled) {
Log.i("WallpaperJobService","Returned");
return;
}
try {
//Doing some work here
} catch (IOException e) {
e.printStackTrace();
}
Log.i("WallpaperJobService", "Job finished!");
jobFinished(params, false);
}
}).start();
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable)
{
Log.i("WallpaperJobService", "Bitmap load failed " +
e.getMessage());
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
} else {
Log.i("WallpaperJobService", "Favorite database is null!");
}
}
#Override
public boolean onStopJob(JobParameters params) {
Log.i("WallpaperJobService", "Job cancelled before completion!");
jobCancelled = true;
return true;
}
}
When doing stuff periodically in the background — JobScheduler, WorkManager, AlarmManager, FCM push messages, etc. — you have to take into account that your process might not be around when it is time for you to do your work. Android will fork a process for you, but it is "starting from scratch". Anything that your UI might have set up in memory, such as a database, would have been for some prior process and might not be set up in the new process.

Android Service only runs whilst debugging

Background
I am creating a service that syncs a local Realm database (stored on phone) with an online database. The database stores users and measurements.
I initialise the service by calling 'startService(new Intent(this, SyncService.class));' during the splash activity's onCreate() method, and specify in the manifest that the service should run for the length of the application.
The service has a broadcast receiver. When the receiver detects a network change from 'not connected' to 'connected', it fires a method called syncDatabases().
This method finds all measurements recorded locally after the timestamp of the last API callback, and sends them to the database. The API responds to a request by returning the object + a unique ID.
When a measurement is made whilst the device is offline, it is stored locally. When an internet connection is made, the syncDatabases() method should be called in order to update the online server with the local measurements.
My steps...
Steps when debugging the project:
With wifi I open the app and with an external device make a new measurement. This appears on both the app and in the database. I then turn wifi off and make another measurement - this appears on the device.
I attach the debugger.
I turn back on wifi and this triggers the services' receivers' onReceive() method. I step through this and it all works according to plan. I reach the syncDatabases() method, and from there I receive the callback from the API, and it then updates the Realm database with the new ID value.
The problem...
If I don't attach the debugger, nothing happens. The new measurements aren't pushed to the database, and none of my Log.e calls are printed.
Why is this happening? And is there an alternative solution / fix for this problem?
Code
Service class
public class SyncService extends Service {
private static final String TAG = "SYNCSERVICE";
private boolean mConnected = false;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getExtras() != null) {
final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo != null) {
switch (netInfo.getState()) {
case CONNECTED:
if (!mConnected) {
Log.e("NETWORK", "Network " + netInfo.getTypeName() + " now connected");
syncDatabases();
mConnected = true;
}
break;
default:
mConnected = false;
break;
}
} else mConnected = false;
}
}
};
#Override
public void onCreate() {
super.onCreate();
initReceiver();
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager.getActiveNetworkInfo() != null) {
mConnected = true;
}
android.util.Log.e(TAG, "onCreate: SyncService created");
}
#Override
public void onDestroy() {
super.onDestroy();
unInitReceiver();
android.util.Log.e(TAG, "onDestroy: SyncService destroyed");
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
android.util.Log.e(TAG, "onBind: SyncService bound");
return null;
}
#Override
public boolean onUnbind(Intent intent) {
android.util.Log.e(TAG, "onUnbind: SyncService unbound");
return super.onUnbind(intent);
}
#Override
public void onRebind(Intent intent) {
super.onRebind(intent);
android.util.Log.e(TAG, "onRebind: SyncService rebound");
}
private void initReceiver() {
IntentFilter filters = new IntentFilter();
filters.addAction("android.net.wifi.WIFI_STATE_CHANGED");
filters.addAction("android.net.wifi.STATE_CHANGE");
registerReceiver(mReceiver, filters);
}
private void unInitReceiver() {
unregisterReceiver(mReceiver);
}
public void syncDatabases() {
RealmResults<UserDB> users = RealmDB.getInstance(getApplicationContext()).where(UserDB.class).findAll();
if (users.size() > 0) {
int userId = users.get(0).getmUserID();
Log.e("MESSAGE", PreferenceUtils.getInstance().getLastSyncDate());
Date lastSync = null;
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault());
try {
lastSync = sdf.parse(PreferenceUtils.getInstance().getLastSyncDate());
}
catch (ParseException e) {
e.printStackTrace();
try {
lastSync = BaseFragment.FORMAT.parse(PreferenceUtils.getInstance().getLastSyncDate());
}
catch (ParseException e1) {
e1.printStackTrace();
}
}
if (lastSync != null) {
Date lastSyncOffset = new Date(lastSync.getTime() + 1000);
/** Get all local results which have been made after the last sync date
 **/
RealmResults<MeasurementDB> newLocalMeasurements = RealmDB.getInstance(getApplicationContext())
.where(MeasurementDB.class).equalTo("user_ID", userId)
.greaterThan("dateCreated", lastSyncOffset)
.findAll();
/** For each measurement made after the last sync, add it to the server
 **/
for (MeasurementDB measurement : newLocalMeasurements) {
TemperatureListener mListener = new TemperatureListener(measurement);
ApiRequest.getInstance(getApplicationContext()).registerNewMeasurement(measurement.getAverage(),
measurement.getDateCreated().toString(), mListener, mListener);
}
}
}
}
/**
* Temperature listener receives the local copy of the temperature item. onResponse can then
* directly mutate the object instead of searching local db
*/
private class TemperatureListener implements Response.Listener<Measurement>, Response.ErrorListener {
private MeasurementDB measurement;
public TemperatureListener(MeasurementDB measurement) {
this.measurement = measurement;
}
#Override
public void onErrorResponse(VolleyError error) {
Log.e("OnResponse", "Failure");
}
#Override
public void onResponse(Measurement response) {
Log.e("OnResponse", "Success");
/** Update our local measurement's ID value (supplied by server)
 **/
RealmDB.getInstance(getApplicationContext()).beginTransaction();
measurement.setMeasurement_ID(response.getmMeasurementId());
RealmDB.getInstance(getApplicationContext()).commitTransaction();
/** Update the last sync date
 **/
PreferenceUtils.getInstance().setLastSyncDate(response.getmDateCreated());
}
}
}
Initialisation of Service in splash activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mTimedOut = true;
finishActivity();
}
}, DURATION);
/** Will sync application / cloud databases in background of app when network connected. **/
startService(new Intent(this, SyncService.class));
doApiWork();
}
Manifest entry
Stop with task to kill the service at the same time as the app.
Exported 'false' stops other apps from using the service.
<service
android:name=".network.SyncService"
android:stopWithTask="true"
android:enabled="true"
android:exported="false"/>
EDIT
I removed the service and left a receiver class, registered in the manifest, which triggers methods on another class when needed. However the receiver is only triggered in debug mode.

requestLocationUpdates generates location by wrong time

I'm trying to get locations from 2 different providers: network and GPS.
I'm using minTime 1 minute and minDistance 300 meters in requestLocationUpdates. With those parameters I don't expect to ever get more than one update per minute (per provider). The problem is, I am getting updates more frequently than that (more than 1 per minute). Why?
Here is some code to demonstrate:
mLocationManager.removeUpdates(listener);
if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, TEN_SECONDS*6, TEN_METERS*30, listener);
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, TEN_SECONDS*6, TEN_METERS*30, listener);
Here is the listener:
private final LocationListener listener = new LocationListener()
{
#Override
public void onLocationChanged(Location location)
{
if(location.getProvider().equals(LocationManager.NETWORK_PROVIDER))
updateUILocation(location,LocationService.this.gpsLocation);
else
updateUILocation(LocationService.this.networkLocation, location);
}
#Override
public void onProviderDisabled(String provider) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
UpdateUILocation:
private void updateUILocation(Location networkLocation,Location gpsLocation)
{
Location location = null;
if(gpsLocation == null || gpsLocation.getAccuracy() > 800)
{
if(!(networkLocation == null || networkLocation.getAccuracy() > 800))
location = networkLocation;
}
else
if(networkLocation == null)
{
if(gpsLocation.getAccuracy() < 800)
location = gpsLocation;
}
else
if(gpsLocation.getAccuracy() < networkLocation.getAccuracy() && gpsLocation.getAccuracy() < 500)
location = gpsLocation;
else
if(networkLocation.getAccuracy() < 800)
location = networkLocation;
if(location!=null)
{
if (mGeocoderAvailable)
doReverseGeocoding(location);
}
// Bypass reverse-geocoding only if the Geocoder service is available on the device.
}
The doReverseGeocoding turn the location into text and call the handler:
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
if(msg.what == UPDATE_ADDRESS) // msg.what == 1
{
LocationService.this.address = (String) msg.obj;
new SendLocation(LocationService.this.id,(String)msg.obj); //send the location to the db
LocationService.this.gpsLocation = null; //reset gps value
LocationService.this.networkLocation = null; //reset network value
}
}
};
I tested this application while driving (which means that the minDistance parameter is not a factor) and I received more than 1 location update per minute.
Here are the locations I received while testing (please ignore the locations since it's in hebrew, just look for the time): http://imrip.interhost.co.il/
"The minTime is 1 minute and the minDistance is 300 meter. With those
parameters I never should get 2 locations in less then 1 minute..."
That's simply not true, not pre-JellyBean anyway.
http://developer.android.com/reference/android/location/LocationManager.html
Prior to Jellybean, the minTime parameter was only a hint, and some location provider implementations ignored it. From Jellybean and onwards it is mandatory for Android compatible devices to observe both the minTime and minDistance parameters.
Pre-JB you can get updates MORE FREQUENTLY than your min time specifies, especially if GPS reception is sketchy. See this answer for more details:
requestLocationUpdates interval in Android

Android requestLocationUpdates without minDistance

I have a need to get the gps location every 20 seconds or so, but ignoring the need to have the user move a certain number of meters, I tried setting the minDistance to zero but still no luck, its only when I manually send the location using the location control from DDMS that this gets activated and the location gets updated.
Note - This is run and tested from the emulator and not a real device, for now at least
private final static Long INTERVAL_TIME = new Long(20);
private final static Float METERS_MOVED_VALUE = 0f;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.journey);
Bundle dataBundle = this.getIntent().getExtras();
try {
driverData = new JSONObject(dataBundle.getString("driverData"));
} catch (JSONException e) {
e.printStackTrace();
}
//Set the UI elements into variables
journeyDescription = (TextView)findViewById(R.id.journeyDescription);
journeyText = (TextView)findViewById(R.id.journeyText);
confirmButton = (Button)findViewById(R.id.btnCheck);
//Acquire a reference to the system Location Manager
locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
try{
//Until requests are found keep broadcasting the drivers location and checking for requests
if(!foundRequest){
updateDriverLocationAndCheckForNewDriverRequests(location);
}
//Always keep updating the drivers location
Double latitude = location.getLatitude()*1E6;
Double longitude = location.getLongitude()*1E6;
geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
}catch(JSONException e){
Toast.makeText(JourneyActivity.this, "An unexpected error occurred", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
//Register the listener with the Location Manager to receive location updates can be either gps provider
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, INTERVAL_TIME, METERS_MOVED_VALUE, locationListener);
}
else {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, INTERVAL_TIME, METERS_MOVED_VALUE, locationListener);
}
}
Here is the related code fragment, any ideas on how to trigger the listener without having the user move or me having to send information from the DDMS Location Controls woild be greatly appreciated
Regards,
Milinda
requestLocationUpdates takes milliseconds, not seconds.
You should change:
private final static Long INTERVAL_TIME = new Long(20);
private final static Float METERS_MOVED_VALUE = 0f;
to:
private final static int INTERVAL_TIME_SECONDS = 20 * 1000; // 20 seconds
private final static float INTERVAL_DISTANCE_METERS = 0f; // 0 meters
As far as testing on the emulator goes. I think your limited to manually pushing GPS locations or creating a few automated simulators:
Testing GPS in Android

Categories

Resources