Wait for an async callback in a periodic task? - android

I'd like to check the user's location every 4 hours or so, and I don't want to leave my app running to do this. It looks like using a GcmTaskService with a PeriodicTask will let my service get called (WakefulBroadcastReceiver has restrictions against starting tasks when the app is stopped in Android 6+), and it will be compatible back to Android 4.4 (unlike JobScheduler) - my lowest supported Android version.
The issue is that GcmTaskService's onRunTask method is synchronous, but I want to use that to ask for a location and process the result (LocationManager will call my LocationListener implementation asynchronously).
How should this be handled?

Use a simple wait/notify:
private interface RunnableLocationListener extends Runnable, LocationListener {}
#Override
public int onRunTask (TaskParams params) {
final Object monitor = new Object();
final AtomicBoolean located = new AtomicBoolean();
new Thread(new RunnableLocationListener() {
Context context = PeriodicCollector.this;
public void run() {
Criteria criteria = new Criteria();
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
LocationManager locationManager = locationManager = (LocationManager)PeriodicCollector.this.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestSingleUpdate(criteria, this, Looper.getMainLooper());
}
#Override
public void onLocationChanged(Location location) {
// handle location here
synchronized (monitor) {
located.set(true);
monitor.notifyAll();
}
}
#Override public void onProviderDisabled(String s) {}
#Override public void onProviderEnabled(String s) {}
#Override public void onStatusChanged(String s, int i, Bundle b) {}
}).start();
int status = GcmNetworkManager.RESULT_FAILURE;
try {
synchronized (monitor) {
if (!located.get()) {
monitor.wait();
}
}
} catch (InterruptedException e) {
status = GcmNetworkManager.RESULT_SUCCESS;
}
return status;
}

Related

Looper.prepare exception using LocationManager in external Service

I am getting the following exception when I try to use LocationManager within a custom class running in an external service:
*** Uncaught remote exception! (Exceptions are not yet supported across processes.)
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.location.LocationManager$GpsStatusListenerTransport$1.<init>(LocationManager.java:1464)
at android.location.LocationManager$GpsStatusListenerTransport.<init>(LocationManager.java:1464)
at android.location.LocationManager.addGpsStatusListener(LocationManager.java:1503)
at org.poseidon_project.contexts.hardware.GPSIndoorOutdoorContext.start(GPSIndoorOutdoorContext.java:97)
at org.poseidon_project.context.management.ContextManager.addObserverRequirement(ContextManager.java:97)
at org.poseidon_project.context.reasoner.ContextMapper.registerIndoorOutdoorsContext(ContextMapper.java:260)
at org.poseidon_project.context.reasoner.ContextMapper.registerContext(ContextMapper.java:197)
at org.poseidon_project.context.ContextReasonerCore.addContextRequirement(ContextReasonerCore.java:70)
at org.poseidon_project.context.ContextReasonerService$1.addContextRequirement(ContextReasonerService.java:74)
at org.poseidon_project.context.IContextReasoner$Stub.onTransact(IContextReasoner.java:74)
at android.os.Binder.execTransact(Binder.java:446)
Now, I have read many answer relating back to the use of Looper, with stuff like:
Looper.prepare;
mLocationManager.requestLocationUpdates(mProvider, mMinTime, mMinDistance, this, Looper.getMainLooper);
But this ends up not causing the Callback (onLocationChanged(Location location)) to be called when there is an update?
The class implements the LocationListener, which also invokes the LocatioManager methods:
public abstract class LocationContext extends ContextObserver implements LocationListener {
protected LocationManager mLocationManager;
private int mMinTime = 3000;
private int mMinDistance = 10;
private String mProvider = LocationManager.GPS_PROVIDER;
private String mIdealProvider = LocationManager.GPS_PROVIDER;
public LocationContext (Context c) {
super(c);
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
}
public LocationContext (Context c, ContextReceiver cr) {
super(c, cr);
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
}
public LocationContext (Context c, ContextReceiver cr, int minTime, int minDistance, String name) {
super(c, cr, name);
mMinTime = minTime;
mMinDistance = minDistance;
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
}
public LocationContext (Context c, ContextReceiver cr, int minTime, int minDistance, String provider, String name) {
super(c, cr, name);
mMinTime = minTime;
mMinDistance = minDistance;
mProvider = provider;
mIdealProvider = provider;
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
}
#Override
public boolean start() {
//new Thread(new Runnable() {
// #Override
// public void run() {
//Looper.prepare();
mLocationManager.requestLocationUpdates(mProvider, mMinTime, mMinDistance, this);
//handler.sendEmptyMessage(0);
//Looper.loop();
// }
//}).start();
mIsRunning = true;
//Looper.loop();
return true;
}
#Override
public boolean pause() {
return stop();
}
#Override
public boolean resume() {
return start();
}
#Override
public boolean stop() {
mLocationManager.removeUpdates(this);
mIsRunning = false;
return true;
}
#Override
public void onLocationChanged(Location location) {
checkContext(location);
}
protected abstract void checkContext(Location location);
#Override
public void onProviderDisabled(String provider) {
if (provider.equals(mIdealProvider)) {
mProvider = LocationManager.GPS_PROVIDER;
if (! mLocationManager.isProviderEnabled(mProvider)) {
Intent gpsOptionIntent = new Intent (android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mContext.startActivity(gpsOptionIntent);
}
}
}
#Override
public void onProviderEnabled(String provider) {
if ((provider.equals(mIdealProvider)) && (! provider.equals(mProvider))) {
mLocationManager.removeUpdates(this);
mProvider = provider;
mLocationManager.requestLocationUpdates(mProvider, mMinTime, mMinDistance, this);
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
public int getMinTime() {
return mMinTime;
}
public void setMinTime(int mMinTime) {
this.mMinTime = mMinTime;
}
public int getMinDistance() {
return mMinDistance;
}
public void setMinDistance(int mMinDistance) {
this.mMinDistance = mMinDistance;
}
I don't really understand how to use the Looper in my situation. Can someone help? I understand the answer "run in UI thread" but this is a single service app, there is no UI, so I don't think I can do it in the UI thread?
****UPDATE*****Solution Found******
I believe I found a solution. The class in question was an abstract class, which I was extending by a few classes that did various Location based operations.
In the LocationContext abstract class I used:
mLocationManager.requestLocationUpdates(mProvider, mMinTime, mMinDistance,this, Looper.getMainLooper());
And in an implementation class (for example one for analysing GPS satellite status) I placed it in a new Thread:
new Thread(new Runnable() {
#Override
public void run() {
Looper.prepare();
GPSIndoorOutdoorContext.super.start();
mLocationManager.addGpsStatusListener(gpsListener);
Looper.loop();
}
}).start();
mLocationManager.requestLocationUpdates(mProvider, mMinTime, mMinDistance, this);
is getting called from a NON UI Thread. Make sure you call your init or call your method in the UI Thread. You're probably initiating LocationContext or calling start method from a NON UI Thread, which you can't do. To request location updates, it must be called from the UI Thread.

Android Service Geolocation

I have one Android Service. This Service launch one class for geolocation. This is the code.
public class Localizar implements LocationListener {
private LocationManager manejador;
private Context context;
private DBAdapter db;
public Localizar(Context context) {
this.context = context;
db = new DBAdapter(context);
}
public void start() {
manejador = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if (manejador.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
manejador.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 500, 0, this);
}else {
}
}
public void stop() {
manejador.removeUpdates(this);
}
private void saveData(double longitude, double latitude) {
Toast.makeText(context, "G R A B A N D O D A T O S", Toast.LENGTH_LONG).show();
}
public void onLocationChanged(Location location) {
saveData(location.getLatitude(), location.getLongitude());
}
public void onProviderDisabled(String proveedor) {
}
public void onProviderEnabled(String proveedor) {
}
public void onStatusChanged(String arg0, int arg1, Bundle bundle) {
}
}
My problem is that the method onLocationChanged never executed. What have I done wrong ?
Thank you!
I tried it on another phone and it works correctly, maybe my phone is broken. I have a samsung galaxy DUOS.....
There is a lot reasons why you don't get onLocationChanged
If you run it in Service try add Looper.getMainLooper(), like:
manejador.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
500,
0,
this,
Looper.getMainLooper()
);

Problems with getting GPS coordinates using AsyncTask

I have tried to get this code sorted couple of times. Here’s the scenario I have tried so far…
Scenario:
Once the activity is started I want to get the coordinates (long & lat) based on network/gps provider. This should run in background and it should keep checking until long & lat is not null or “0.0”. Thus, I have tried the following code with AsyncTask and thereby using LocationListener in doInBackground method.
Source Code:
public class GetLocation extends AsyncTask<String, String, String>
{
private myTest test;
boolean running =true;
private Context cont;
String addressString;
public GetLocation(myTest fr, Context contxt)
{
test = fr;
cont = contxt;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(String result)
{
test.GetContent();
}
#Override
protected String doInBackground(String... params) {
Looper.myLooper().prepare();
LocationManager locationManager;
locationManager = (LocationManager) cont
.getSystemService(Context.LOCATION_SERVICE);
Criteria crta = new Criteria();
crta.setAccuracy(Criteria.ACCURACY_FINE);
crta.setAltitudeRequired(false);
crta.setBearingRequired(false);
crta.setCostAllowed(true);
crta.setPowerRequirement(Criteria.POWER_LOW);
String provider = locationManager.getBestProvider(crta, true);
Location location = locationManager.getLastKnownLocation(provider);
updateWithNewLocation(location);
LocationListener locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
updateWithNewLocation(location);
}
#Override
public void onProviderDisabled(String provider) {
updateWithNewLocation(null);
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
}
};
locationManager.requestLocationUpdates(provider, 0, 0,
locationListener);
Looper.loop();
return addressString;
}
private void updateWithNewLocation(Location location) {
Constants.lat = Double.toString(location.getLatitude());
Constants.long = Double.toString(location.getLongitude());
}
}
Problem:
However, I also know that Looper’s can help to keep the thread active. I want to be able to get valid coordinates and this should loop until its received. How do i call to get the locations over and over again until valid one's are received? (I could put conditions within the loops i have provided but im not aware as to how and what methods i should call to achieve this). Please provide a code snippet if possible.
Cheers!
You have implemented this wrong. There is no need to create the AsyncTask. The LocationManager will asynchronously fetch locations for you and deliver them on your LocationListener.onLocationChanged. Once you get a proper Location you can call LocationManager.removeUpdates() and this will stop for further delivery of Locations.
Do not use AsyncTask for this. You just registered the LocationManager that is listen by LocationListener and use progressbar for that. Once call LocationListner.onLocationChanged try to get Lat and Long and dismiss the progress bar. OnLocationChanged will be call at you move your device for a short distance.

Get Current Location From Android Background Service Issue

I want to develop location based reminder app. Therefore I want to use android service for get current location even app is not running. But I didn't do. I set timer in android service class but I don't know how to get current location in service class. What is the problem ? I got some error like this:
can't create handler inside thread that has not called looper.prepare()
public class TrackerService extends Service {
double latShared;
double lngShared;
double latService;
double lngService;
private LocationManager lm;
Timer timer;
Handler handler;
final static long TIME = 15000;
SharedPreferences mSharedPrefs;
SharedPreferences.Editor mPrefsEditor;
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate() {
mSharedPrefs = getSharedPreferences("locationXML", MODE_PRIVATE);
latShared = (double)mSharedPrefs.getFloat("lat", 0);
lngShared = (double)mSharedPrefs.getFloat("lng", 0);
timer = new Timer();
timer.schedule(new TimerTask(){
#Override
public void run(){
LocationUpdates();
}
},0,TIME);
}
public void LocationUpdates(){
locListener locList = new locListener();
lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locList);
}
#Override
public void onDestroy() {
//lm.removeUpdates(this);
timer.cancel();
}
public class locListener implements LocationListener{
#Override
public void onLocationChanged(Location location) {
latService = location.getLatitude();
lngService = location.getLongitude();
}
#Override
public void onProviderDisabled(String provider) {}
#Override
public void onProviderEnabled(String provider) {}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
}
}
LocationManager.requestLocationUpdates is supposed to run on UI thread as its sibling method defines. On the other hand TimerTask introduces a new thread to offload its execution.
However, if you are not using a new process for your service then simply call LocationUpdates() as below:
#Override
protected void onCreate() {
mSharedPrefs = getSharedPreferences("locationXML", MODE_PRIVATE);
latShared = (double)mSharedPrefs.getFloat("lat", 0);
lngShared = (double)mSharedPrefs.getFloat("lng", 0);
final Handler h = new Handler();
h.post(new Runnable() {
#Override
public void run() {
LocationUpdates();
h.postDelayed(this, TIME);
}
});
}
OR, if you don't want to use handler then simply upgrade your requestLocationUpdates as below:
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locList, getMainLooper());
In above case, getMainLooper() will dispatch your location updates from UI thread.

Best error handling approach for GPS lookup in Android?

I'm beta testing my first Android app and have had a few users mention that when they attempt to lookup by GPS it hangs. In order to improve error handling around this I wanted to get the opinion of people who have apps in the wild.
My current activity does the following to kick off the lookup
findViewById(R.id.gpsButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LocationManager mlocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
MyLocationListener mlocListener = new MyLocationListener();
Criteria locationCriteria = new Criteria();
locationCriteria.setAccuracy(Criteria.ACCURACY_FINE);
mlocManager.requestLocationUpdates(mlocManager.getBestProvider(locationCriteria, true), 0, 0, mlocListener);
}
});
The implementation of my custom location lookup class is below
public class MyLocationListener implements LocationListener {
private boolean alreadyLocatedDevice;
private ProgressDialog dialog;
public MyLocationListener() {
this.dialog = ProgressDialog.show(LocationLookup.this, "", "Loading...");
}
#Override
public void onProviderDisabled(String provider) {
this.dialog.dismiss();
DialogHelper.showDialogWithMessageAndTitle("", "You don't currently have location services turned on", LocationLookup.this);
}
#Override
public void onLocationChanged(android.location.Location location) {
if (!alreadyLocatedDevice) {
alreadyLocatedDevice = true;
Location loc = new Location();
loc.setLng(Double.toString(location.getLongitude()));
loc.setLat(Double.toString(location.getLatitude()));
((AppDelegate) getApplicationContext()).setSelectedLocation(loc);
Intent findKioskLocation = new Intent(LocationLookup.this, FindKioskLocation.class);
this.dialog.dismiss();
startActivity(findKioskLocation);
}
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
//To change body of implemented methods use File | Settings | File Templates.
}
#Override
public void onProviderEnabled(String s) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
And finally I've added both the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions in my manifest file.
Any help would be much appreciated!
Here is a nice implementation that I recnetly looked at. Basically by default it uses GPS to get a location. If no location can be found within a certain time period or no satellites are available it switches to Network.
Hope this helps
A Stacktrace from the logcat would have helped you and other developers here understand where the problem is coming from. Try to ask the users to recreate the problem if possible and find out when it is occurring.
As for guidance with the locaton manager, Google Developers just posted a blog recently and also updated the docs on how to use location manager. Check the documentation here and also the blog post which explains it with an example. That might help you better. The blog post also explains how to use different location providers and how to be user friendly and guidance necessary in most ases when using location in Android applications.
For anyone who might follow this thread -I found a mixture of my own approach (admittedly hackish in this code example) and the one mentioned by #bear to work without any issues (plus the location lookup was fast/accurate and error free)
I found the example listed by #bear to be a little more complex than I needed. For starters I wanted to kick off the GPS lookup when a button was clicked and have a simple async task wrapping this so it would throw up a dialog/etc
Next I wanted the exact latitude and longitude (no need to pass this off to another class because my example was simply to use the lat + lng to locate a resource and plot it)
So if you can follow my untested rather copy/paste approach here goes...
Inside your activity you would spin up the service during an onclick lets say ...
LocationManager networkManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationManager gpsManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationService locationProcessor = new LocationService(YourActivityName.this, networkManager, gpsManager, dialog);
locationProcessor.onStartCommand();
Now the location service itself
package com.epicsoftware.android.global;
import android.app.ProgressDialog;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import com.epicsoftware.android.activity.LocationLookup;
public class LocationService {
private LocationManager networkLm;
private LocationManager gpsLm;
private LocationListener networkListener;
private LocationListener gpsListener;
private boolean isRunning;
private boolean networkLocDisabled;
private boolean gpsLocDisabled;
private Context activity;
private LocationManager tmpNetworkManager;
private LocationManager tmpGpsManager;
private Handler locationHandler;
private ProgressDialog dialog;
private boolean gpsUpdated;
private boolean done;
public LocationService(final Context activity, LocationManager networkManager, LocationManager gpsManager, ProgressDialog dialog) {
this.tmpNetworkManager = networkManager;
this.tmpGpsManager = gpsManager;
this.activity = activity;
this.dialog = dialog;
}
public void onStartCommand() {
if (!isRunning) {
isRunning = true;
startLocationListeners();
locationHandler = new Handler();
getLocationByZip.start();
}
}
private void startLocationListeners() {
networkListener = new NetworkLocationListener();
gpsListener = new GpsLocationListener();
networkLm = tmpNetworkManager;
gpsLm = tmpGpsManager;
networkLm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, networkListener);
gpsLm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener);
}
private Thread getLocationByZip = new Thread() {
public void run() {
try {
for (int i = 0; i < 15;) {
if (!locationDisabled() || !gpsUpdated) {
try {
Thread.sleep(1000);
} catch (Exception e) {
break;
}
i++;
} else {
break;
}
}
locationHandler.post(monitorTheNetworkAndGpsProviders);
} catch (Exception e) {
killService();
done = true;
}
}
};
private Runnable monitorTheNetworkAndGpsProviders = new Runnable() {
#Override
public void run() {
killService();
dialog.dismiss();
if (!done) {
done = true;
((LocationLookup) activity).warnUserThatLocationServicesAreDisabledOrFailed();
}
}
};
private boolean locationDisabled() {
if (gpsLocDisabled && networkLocDisabled) {
done = true;
((LocationLookup) activity).warnUserThatLocationServicesAreDisabledOrFailed();
return true;
} else {
return false;
}
}
private void updateDb(Double lat, Double lon) {
done = true;
((LocationLookup) activity).setLocationDataAndSpinUpNextActivity(lat, lon);
}
public void killService() {
networkLm.removeUpdates(networkListener);
gpsLm.removeUpdates(gpsListener);
}
public class NetworkLocationListener implements LocationListener {
#Override
public void onLocationChanged(Location location) {
if (location != null) {
updateDb(location.getLatitude(), location.getLongitude());
}
}
#Override
public void onProviderDisabled(String provider) {
networkLocDisabled = true;
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
}
public class GpsLocationListener implements LocationListener {
#Override
public void onLocationChanged(Location location) {
if (location != null) {
gpsUpdated = true;
updateDb(location.getLatitude(), location.getLongitude());
}
}
#Override
public void onProviderDisabled(String provider) {
gpsLocDisabled = true;
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
}
}

Categories

Resources