Right, the situation is as follows;
We have developed an app that checks the location of the user every 5-10 minutes for location specific content. To do so, I've created a Service to stick to the background (so it can update even when the app isn't directly in the foreground) in which a LocationController is created to check the latest known location. When it's finished, it cleans up and the location is sent to a database (which is a necessity for this concept).
This all works fine, as long as I check the location with GPS_PROVIDER. However, when I switch it around to NETWORK_PROVIDER, it may check the location once more before dying completely.
I've tried multiple options to prevent this problem, but nothing seems to be working with the update service when I swap that 1 setting.
Here are a few snippets which should be relevant to the service:
UpdateService:
#Override
public void onCreate() {
super.onCreate();
Log.d("Debug", "Created service");
PowerManager mgr = (PowerManager)getSystemService(Context.POWER_SERVICE);
wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
wakeLock.acquire();
IntentFilter filter = new IntentFilter();
filter.addAction(UPDATE_ACTION);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("Starting", "Starting Service");
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(UPDATE_ACTION);
alarmManagerPendingIntent = PendingIntent.getBroadcast(this, 0, i, 0);
alarmManagerPendingIntent.cancel();
mAlarmManager.cancel(alarmManagerPendingIntent);
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
AlarmManager.INTERVAL_FIFTEEN_MINUTES,
AlarmManager.INTERVAL_FIFTEEN_MINUTES,
alarmManagerPendingIntent);
mLocationController = new LocationController(this, this);
updateHandler = new Handler();
return START_STICKY;
}
LocationController:
private LocationListener locationListener = new LocationListener() {
private boolean didSendLocation;
public void onLocationChanged(Location location) {
mLastLocation = location;
mCallback.locationUpdate(location,false);
didSendLocation = true;
}
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d("Debug", "Status changed: "+status);
Location _lastLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Log.d("Debug", "Last location: "+_lastLocation);
if(!didSendLocation)
{
mCallback.locationUpdate(_lastLocation,false);
}
didSendLocation = false;
}
public void onProviderEnabled(String provider) {
Log.d("Debug", "Provider Enabled");
}
public void onProviderDisabled(String provider) {
Log.d("Debug", "Provider disabled");
}
};
public LocationController(Context mContext, LocationControllerCallback callback) {
this.mContext = mContext;
mCallback = callback;
Log.d("Debug", "Created LocationController");
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 100, locationListener);
mLastLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
mCallback.locationUpdate(mLastLocation,true);
}
So, the code works as it is now, but when I swap:
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 100, locationListener);
with
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 300000, 100, locationListener);
It will no longer update. Any ideas?
When you use this to get location updates:
mLocationManager.requestLocationUpdates(...)
you don't need AlarmManager methods as the first parameter of the requestLocationUpdates method already acts as a "timer". See here for more info.
Also, you may need to include the following directives in your AndroidManifest.xml file:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CONTROL_LOCATION_UPDATES"/>
Without these permissions, the requestLocationUpdates method may not work as you expected regardless of which provider you use.
UPDATE 1:
I would have your LocationController class initialiased in a Service, for example:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
this.mLocationController = new LocationController(this);
return Service.START_NOT_STICKY;
}
Then, start the above Service in the onResume method of an activity (MainActivity or another activity in your project), and pause the Service in the onPause method of the same activity.
If its specific to the NETWORK_PROVIDER, you may be seeing this bug:
https://code.google.com/p/android/issues/detail?id=57707
Original bug report included the Samsung Galaxy S3 as an affected device.
To help troubleshoot - you may also want to try selecting the NETWORK_PROVIDER in the GPS Benchmark app (full disclosure - its my app) to see if this app exhibits the same behavior. If so, its likely an issue with the device.
If you determine you're seeing the above issue, please be sure to star it on the issue page if you'd like to see it fixed.
I've solved the problem by switching to the Google Play Fused Location provider. A more detailed post around this can be found here: https://stackoverflow.com/a/19282976/3532181
Sadly the standard network provider just seemed too unreliable when it came to updates, ranging from an update within 8 minutes to hours before a new update was sent to the app.
I had a similar issue. I was able to fix this by adding the required permissions. The issue is related to new background service limitations.
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Check this documentation for better understanding.
Related
I have created a service which finds and then stores the user's coordinates in an SQLite database.
public class GPS_Service extends Service {
DatabaseHelper myDb;
private LocationListener locationListener;
private LocationManager locationManager;
private String latitude, longitude;
#Override
public void onCreate() {
super.onCreate();
myDb = new DatabaseHelper(this);
}
#SuppressLint("MissingPermission")
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service")
.setContentText("Coordinates Location Running")
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
Log.d("myTag", "Hello");
latitude = String.valueOf(location.getLatitude());
longitude = String.valueOf(location.getLongitude());
insertCoordinates(latitude, longitude);
Intent i = new Intent("location_update");
i.putExtra("latitude", latitude);
i.putExtra("longitude",longitude);
sendBroadcast(i);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
};
locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, locationListener);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
if(locationManager != null)
locationManager.removeUpdates(locationListener);
}
private void insertCoordinates(String latitude, String longitude) {
boolean inserted = myDb.insertData(latitude, longitude); //Insert coordinates
//Check if insertion is completed
if(inserted)
Toast.makeText(GPS_Service.this, "Coordinates Inserted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(GPS_Service.this, "Coordinates Not Inserted", Toast.LENGTH_SHORT).show();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I can either start or stop the service from the MainActivity like this
private void enable_buttons() {
buttonStartService.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent serviceIntent = new Intent(getApplicationContext(), GPS_Service.class);
//Checks if the SDK version is higher than 26 to act accordingly
ContextCompat.startForegroundService(MainActivity.this, serviceIntent);
}
});
buttonStopService.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent serviceIntent = new Intent(MainActivity.this, GPS_Service.class);
stopService(serviceIntent);
}
});
}
The problem is that when I start this service, if I either completely close the app or leave it in the background, the locationListener will work for 30 seconds and then it will stop. If I reopen the app, the service continues to work from where it stopped. Also I checked in the developer options if the service is running, and it indeed is even though the locationListener doesn't output the expected results. Any ideas?
TL;DR:
Add android:foregroundServiceType="location" to your Service's manifest entry.
EXPLANATION
This new behavior, for Android 10, is exactly as you've described: Even though you may be using a foreground service, 30 seconds after your app leaves the screen -- location updates cease.
You might've noticed that Android 10 devices present two new choices to the user when granting location permissions (for legacy (API < 29) apps, or apps that declare the ACCESS_BACKGROUND_LOCATION permission):
"Allow all the time"
"Allow only while using this app"
"Allow only while using this app" effectively means, "Allow while the app is visible onscreen". That's because the user now has the option of selectively removing location access -- even to a foreground service -- based on that criteria. Users can change this setting at any time, even if your app is running.
The Android docs explain that the solution, android:foregroundServiceType="location", was intended for your precise use case: "Google Maps"-like apps, which have a foreground service, but are expected to continue processing location data if the user switches to another app. The technique is called "continuing a user-initiated action", and it allows you to get location updates even after your app is placed in the "background".
(The docs seem to be expanding the definition of the term "background", here. In the past, if you had a foreground service, your app was considered "in the foreground" -- at least for the purposes of task priority, Doze, and so forth. Now it appears that an app is considered "in the background", with respect to location access, if it hasn't been onscreen in the last 30 seconds.)
I am not sure what UI changes (like in the Google Play store) take place when you set a particular foregroundServiceType. Regardless, it seems to me that users are unlikely to object.
OTHER SOLUTIONS FOR ANDROID 10 DEVICES
Alternatively, you could've declared a targetSdkVersion of 28 or less, which will let your app function in a location "compatibility mode".
You also have the option of gaining the ACCESS_BACKGROUND_LOCATION permission, but the docs caution against this:
If your app doesn't require location access while running in the background, it's highly recommended that you not request ACCESS_BACKGROUND_LOCATION...
This approach isn't required, for your use case, because your Activity is used to start your Service; you can be guaranteed that your app has been onscreen at least once before the Service starts getting background location updates. (At least, I assume that that is how the OS determines the start of a "user-initiated action". Presumably, the foregroundServiceType approach won't work if you're starting the Service from a JobScheduler, or a BroadcastReceiver, or something.)
PS: Hang on to that WakeLock code. You're going to need to keep the device awake, if you want to keep getting updates at a steady 10-second pace.
I dont really see any problem in the code, but I am a bit sceptical about START_NOT_STICKY. Try START_STICKY instead.
START_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), then leave it in the
started state but don't retain this delivered intent. Later the system
will try to re-create the service. Because it is in the started state,
it will guarantee to call onStartCommand(Intent, int, int) after
creating the new service instance; if there are not any pending start
commands to be delivered to the service, it will be called with a null
intent object, so you must take care to check for this.
START_NOT_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), and there are no new
start intents to deliver to it, then take the service out of the
started state and don't recreate until a future explicit call to
Context.startService(Intent). The service will not receive a
onStartCommand(Intent, int, int) call with a null Intent because it
will not be re-started if there are no pending Intents to deliver.
So as you are returning START_NOT_STICKY, if the process is killed, onStartCommand() will not be called again, which is where you initialize both the listener and the locationManager.
Google Play Services' Fused Location Provider Api lets you request location updates with location listeners or pending intents. I can successfully request location updates with the location listener but I have been struggling to replicate the same behaviour with pending intents. For the latter, I launch an intent service which handles the location data. What I have noticed during testing is that the location updates correspond with the interval I set in the location request. However, as time goes by the interval between updates increases tremendously even though the interval in the location request has remained constant. I have noticed this behaviour on several occasions with multiple devices. Does anyone have an idea what could be going on?
Foreground location tracking
protected void requestLocationUpdates()
{
mIsTracking = true;
LocationRequest locationRequest = mLocationRequests.get(mPreviousDetectedActivity.getType());
if (locationRequest != null)
{
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, locationRequest, this);
}
}
#Override
public void onLocationChanged(Location location)
{
Log.i(TAG, "onLocationChanged");
handleLocationChanged(location);
}
Background location tracking
protected void requestLocationUpdates()
{
LocationRequest locationRequest = mLocationRequests.get(mPreviousDetectedActivity.getType());
if (locationRequest != null)
{
mResultCallabackMessage = "Request location updates ";
PendingIntent pendingIntent = getLocationPendingIntent();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
locationRequest, pendingIntent).setResultCallback(this);
}
}
protected PendingIntent getLocationPendingIntent()
{
if (mLocationPendingIntent != null) return mLocationPendingIntent;
Intent intent = new Intent(this, LocationUpdatesIntentService.class);
mLocationPendingIntent = PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return mLocationPendingIntent;
}
public class LocationUpdatesIntentService extends IntentService
{
public LocationUpdatesIntentService()
{
// Use the TAG to name the worker thread.
super(TAG);
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
protected void onHandleIntent(Intent intent)
{
Bundle bundle = intent.getExtras();
Location location = (Location) bundle.get(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
handleLocationUpdates(location);
}
}
Any help is greatly appreciated.
It could be because some other application in the device must have requested for more frequent updates. Please refer this link for more details
This interval is inexact. You may not receive updates at all (if no
location sources are available), or you may receive them slower than
requested. You may also receive them faster than requested (if other
applications are requesting location at a faster interval). The
fastest rate that that you will receive updates can be controlled with
setFastestInterval(long)
I'm having the same problem too, but as #Shiv said you should be testing different parameters:
mClient.requestLocationUpdates(LocationRequest.create(), mLocationIntent)
try this:
mClient.requestLocationUpdates(createLocationRequest(), mLocationIntent)
private LocationRequest createLocationRequest() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(UPDATE_INTERVAL);
locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setMaxWaitTime(MAX_WAIT_TIME);
return locationRequest;
}
Im trying to get my GPS working in a service but its not working. The GPS bit works on its own but not in the service.
I have tried debugging with System.out.println() and fond where it stops working but cant work out why it all looks good.
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("test 1");
LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
System.out.println("test 2");
LocationListener lli = new myLocationListener();
System.out.println("test 3");
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, lli);
System.out.println("test 4");
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
class myLocationListener implements LocationListener{
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
if(location != null){
pet.setMeter();
}
}
It gets to Test 4 then dies. I am lost at why so if anyone can help that would be awesome thanks.
A nice gps tracker guide: http://www.androidhive.info/2012/07/android-gps-location-manager-tutorial/
Just needed to add this.mLocation = location in onLocationChanged
He also wraps it into a service.
I have registered my LocationManager for location updates, every 10 seconds
mgr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10 * 1000, 50, this);
But the onLocationChanged callback returns a location every 10 secs, which(the location) is more than 2 hours old. And that time-stamp is never changing.
The problem is:
2 hours back I was in a complete different location(home) where I used the device on a wifi. Now currently I am in some other location(office) on a different wifi where my application shows my current location as home. Same thing happened at home yesterday, when it was showing office as my current location. It got to work(started showing correct location) when I closed my app, opened FourSquare app and re-opened my app.
Complete Code:
public class LocationService extends Service implements LocationListener {
public static double curLat = 0.0;
public static double curLng = 0.0;
private LocationManager mgr;
private String best;
private Location location;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
mgr = (LocationManager) getSystemService(LOCATION_SERVICE);
best = LocationManager.NETWORK_PROVIDER;
location = mgr.getLastKnownLocation(best);
if (location != null) {
dumpLocation(location);
mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
10 * 1000, 50, this);
}
return START_NOT_STICKY;
}
}
private void dumpLocation(Location l) {
SimpleDateFormat s = new SimpleDateFormat("dd/MM/yyyy:hh:mm:ss",
Locale.ENGLISH);
String format = s.format(l.getTime());
//The above time is always 28/03/2013:09:26:41 which is more than 2 hrs old
curLat = l.getLatitude();
curLng = l.getLongitude();
}
#Override
public void onLocationChanged(Location location) {
dumpLocation(location);
}
#Override
public void onProviderDisabled(String provider) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
}
Being started in an Activity this way:
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent i = new Intent(this, LocationService.class);
pi = PendingIntent.getService(this, 0, i,
PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), 10000, pi);
Permissions in manifest:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
I can get the correct location now, by opening some other location based app like Maps, Navigator, Foursquare etc.., But why my app isn't able to get a new/fresh fix from the provider.
Thank You
You are getting old location because of this line
location = mgr.getLastKnownLocation(best);
because if GPS is not enabled then it will show you the old location . So remove this code It will work like a champ
You can also refer to this library
https://github.com/nagendraksrivastava/Android-Location-Tracking-Library
On the basis of your comments I have edited the answer Okay Let me explain line by line
location = mgr.getLastKnownLocation(best); it will give you object of last know location then it will go inside if condition and it will call dumplocation and will get last location data and after that you called
mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
10 * 1000, 50, this);
but suppose GPS provider is disabled then it will not fetch new location so you will get old location only . So either you can change it like
if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
{
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER,new NagendraSingleLocationListener(),null);
}
else
{
locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER,new NagendraSingleLocationListener(),null);
}
I think because you cancel your pending intent right away, thus the requestLocationUpdate would not start update before you cancel. Why don't you sleep may be for 2 second before cancel.
From my experience android will give you a location when you request the updates even if gps has not enough sattelites to work. So even if gps is on - if you are inside or in a location that is bad (like under a bridge) android will deliver an old fix to you. Can be a very old one indeed.
The only thing I found to be working 100% is to generelly not use the first position but only remember the time. When new positions arrive you can check that the time is newer than the last. If you want to only use very precise positions you may also need to check that location.getAccuracy() is low (the lower the better).
I use gps to get my timestamps for a soap interface as the android clock can be very off sometimes and this was the only way for me to get a valid time from gps.
I am creating an Android application that stores a couple of conditions like Location, Time, Contact and Battery level. What I intend for my application to do is to store these conditions along with some phone setting changes that the user wants to activate if these conditions are met. It would be better if I put my questions in a list :
How do I check the Location of the device at regular intervals ? Is there a listener service (like for the Contacts option the TelephonyManager can be used) that can be used to listen for a callback in case the device location changed ?
Is there a way to schedule a particular function to be called based on Time in Android ?
Is there a way to call a particular function when the battery level of the device changes or the device is charging or unplugged ?
Here's your answer point wise:
1) You can use LocationManager to get the location of your device.
LocationManager locationManager = (LocationManager)
this.getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new LocationListener()
{
public void onLocationChanged(Location location)
{
makeUseOfNewLocation(location); //here you get the new location
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
You can also use GeoCoder class to exactly point out the address using the longitude and latitude received from locationManager.
Detail tutorial here - LocationManager
2) You can schedule the particular function to be called - using BroadcastReceiver and AlarmManager service.
private void setRecurringAlarm(Context context) {
// we know mobiletuts updates at right around 1130 GMT.
// let's grab new stuff at around 11:45 GMT, inexactly
Calendar updateTime = Calendar.getInstance();
updateTime.setTimeZone(TimeZone.getTimeZone("GMT"));
updateTime.set(Calendar.HOUR_OF_DAY, 11);
updateTime.set(Calendar.MINUTE, 45);
Intent downloader = new Intent(context, AlarmReceiver.class);
PendingIntent recurringDownload = PendingIntent.getBroadcast(context,
0, downloader, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) getActivity().getSystemService(
Context.ALARM_SERVICE);
alarms.setInexactRepeating(AlarmManager.RTC_WAKEUP,
updateTime.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, recurringDownload);
}
Detail tutorial BroadCastReceiver and AlarmManager
So, if you want you can recurrently check for the present location of device ( using LocationManager as shown in point 1) inside OnReceive of your BroadCastReceiver.
3) There is a BroadCastReceiver to catch when BatteryLevel changes as below:
private BroadcastReceiver mBatInfoReceiver
= new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent intent) {
int level = intent.getIntExtra("level", 0);
// do something...
}
}
registerReceiver(this.mBatInfoReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
For plugin or charging use following code:
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// How are we charging?
int chargePlug = battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BATTERY_PLUGGED_AC;
I Hope All this will help you...