I have an app with two activity A and B (a google map), both need to periodically receive GPS location. I need that going from activity A to B and back app continues to retrieve the GPS position without interruption. To solve this problem I thought to two ways:
The first is to use a service that starts when app start and continuously retrieve GPS position and somehow notify both activities about new data, then, when the app finish it stop retrieving position.
The second way is to use the fragments, I have only one activity that continuously retrieve GPS position, then I have two fragments that show A and B content and somehow use the position data received from the activity.
When I say "somehow" I want to say that I don't know how :-)
Do you have suggestions on how to implement these two approaches or can you suggest better approaches?
#1 Whenever location changed, send intent and receive it where you want
LocalBroadcastManager mLocalBroadcastManager;
#Override
public void onLocationChanged(Location location) {
Intent intent = new Intent();
intent.putExtra("com.exmaple.sample", location);
mLocalBroadcastManager.sendBroadcast(intent);
}
#2 Whenever location changed, send intent and receive it where you want (same with 1)
#Override
public void onLocationChanged(Location location) {
Intent intent = new Intent();
intent.putExtra("com.exmaple.sample", location);
sendBroadcast(intent);
}
#3 Or, pass a pending intent only one time, then receive locations
Intent intent = new Intent(mContext, YourBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 1, intent, 0);
mLocManager = (LocationManager)getSystemService(LOCATION_SERVICE);
mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, pendingIntent);
#4 Make a static variable, then poll it from an activity periodically. Note that, you should optimize polling frequency because it's not guaranteed getting fresh location. This is maybe be bad approach but it could be worth it for your scenario
public static Location sLastLocation;
#Override
public void onLocationChanged(Location location) {
sLastLocation = location;
}
#5 Use event bus, link
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.
ios sdk has great region monitoring functions. I need something like that in android and i think we have two alternatives. Geofencing and LocationManager.
Geofencing has really tidy examples and bugs , so i prefered LocationManager. Everyting works fine in LocationManager except one. If you add your current location as ProximityAlert , it immediatly fires "ENTERING" , but it is my current location , it doesnt mean that i entered this region. Because of that , it fires "ENTERING" each time i start my application if i am in region.(Even if i am not moving)
How can i solve this problem and fire event only if user is really ENTERING the region ?
Here is how i am adding PeddingIntents for my locations.
LocationManager locationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
for(Place p : places)
{
Log.e("location", p.location);
Bundle extras = new Bundle();
extras.putString("name", p.displayName);
extras.putString("id", p.id);
Intent intent = new Intent(CommandTypes.PROX_ALERT_INTENT);
intent.putExtra(CommandTypes.PROX_ALERT_INTENT, extras);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,Integer.parseInt(p.id), intent,PendingIntent.FLAG_CANCEL_CURRENT);
float radius = 50f;
locationManager.addProximityAlert(p.lat,
p.lon, radius, 1000000, pendingIntent);
}
Receiver
public class ProximityReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
final String key = LocationManager.KEY_PROXIMITY_ENTERING;
final Boolean entering = intent.getBooleanExtra(key, false);
Bundle b = intent.getBundleExtra(CommandTypes.PROX_ALERT_INTENT);
String id = b.getString("id");
Log.e("here" + id, "here");
if (entering) {
Log.e(TAG,"entering");
} else {
Log.e(TAG,"leaving");
}
}
Manifest
<receiver android:name=".ProximityReceiver">
<intent-filter>
<action android:name="ACTION_PROXIMITY_ALERT" />
</intent-filter>
</receiver>
Thank you very much
PS: iOS does not have this problem and their documentation explains it so
Monitoring of a geographical region begins immediately after registration for authorized apps. However, do not expect to receive an event right away. Only boundary crossings generate an event. Thus, if at registration time the user’s location is already inside the region, the location manager does not automatically generate an event. Instead, your app must wait for the user to cross the region boundary before an event is generated and sent to the delegate. That said, you can use the requestStateForRegion: method of the CLLocationManager class to check whether the user is already inside the boundary of a region.
EDIT: since i wrote this, there has been a new thing added to the geofence API, 'setInitialTrigger' that allevates this:
https://developers.google.com/android/reference/com/google/android/gms/location/GeofencingRequest.Builder#setInitialTrigger%28int%29
Yeah, this is a nuisance, and is one of the major points where Android and IOS geofencing differs, unfortunately.
Android alerts when you're inside the Geofence if it knows you were outside before OR you added the geofence while inside it.
The way i solve this is with a 'grace period' in my broadcast reciever. Basically, when i create the Geofence, i store away its creation time in sharedpreferences, and i check against that value in onReceive.
By doing this, any 'immediate' hit will be filtered away. Perhaps 3 minutes is too long for someone else, but it works for me based on how i work with the geofences in my app.
private static final Long MIN_PROXALERT_INTERVAL = 18000l; // 3 mins in milliseconds
...
long geofenceCreationTime = session.getPrefs().getCurrentGeofenceCreation();
long elapsedSinceCreation = now - geofenceCreationTime;
if(elapsedSinceCreation < CREATIONTIME_GRACE_PERIOD){
if (ApplicationSession.DEBUG) {
Log.d(TAG, "elapsedSinceCreation;"+elapsedSinceCreation+";less than;"+CREATIONTIME_GRACE_PERIOD+";exiting");
}
return;
}
Hope you see what i'm getting at.
Hope it helps.
I have searched quite a bit and I'm not totally clueless. I have implemented a temporary solution on my end but was wondering if there is a better approach out there.
I have an app that sends a person's location after every 60 seconds to a server. On my dashboard (the main screen that will go to onPause after application starts), I have registered a LocationManager with the following code:
service = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean enabled = service
.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!enabled)
{
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
else
{
Criteria criteria = new Criteria();
provider = service.getBestProvider(criteria, false);
service.requestLocationUpdates(provider, 10000, 50, this);
Location location = service.getLastKnownLocation(provider);
// Initialize the location fields
if (location != null)
{
onLocationChanged(location);
}
else
{
Log.d("Location: ", "No update received");
}
}
However, as I mentioned, this activity will be minimized by the user (by pressing the home button). There is a service that gets called every 60 seconds by an AlarmManager. That service accesses static variables from the Dashboard Activity (lat, lon) and sends it to the server.
My question:
If the activity goes onPause, will the requestLocationUpdates function stop? Or will it keep working?
If it keeps working, it will keep updating the two lat and lon static String objects and the service will keep getting updated values. If they stop, the service will keep getting the same old values again and again.
Also, is there a better way to approach this problem? Using a mix of GPS Provider and Network Provider? (I need fairly accurate values).
EDIT
Here's my Alarm. This code is inside Login Activity
Intent i = new Intent(con, LocationPoller.class);
i.putExtra(LocationPoller.EXTRA_INTENT, new Intent(con,
Login.class));
i.putExtra(LocationPoller.EXTRA_PROVIDER,
LocationManager.GPS_PROVIDER);
gps = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(con, 0, i, 0);
gps.setRepeating(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(),
10 * 1000, pi);
Log.d("Service: ",
"GPS Service started and scheduled with AlarmManager");
Here's my receiver (also within Login activity)
private class ReceiveMessages extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Location loc = (Location) intent.getExtras().get(
LocationPoller.EXTRA_LOCATION);
String msg;
if (loc == null)
{
msg = intent.getStringExtra(LocationPoller.EXTRA_ERROR);
}
else
{
msg = loc.toString();
}
if (msg == null)
{
msg = "Invalid broadcast received!";
}
Log.d("GPS Broadcast: ", msg);
}
}
Nothing's happening :s Not getting anything on logcat which means the broadcast isn't being received.
When activity goes on pause, all registered listeners will stop. Better way to implement this is, alarm manager sent a broadcast every 60 seconds, this broadcast receiver starts a service and this service will request a location on Wakeful thread, once location information is retrieved, update the location on server.
There is an Open source library available with an example (courtesy CommonsWare), please refer below link. Its under Apache 2.0 license
Location Polling Library
Please find my sample project using above library. I have modified few things in the above library and created my own version.
Location Polling Demo Application
i ve been facing some problems trying to pass data through intents and pending intents to a BroadcastReceiver, concerning proximity alerts. More specifically, am trying to pass an object, that among others holds the user's constantly changing position. I ve tried various tactics being proposed here (and not only) but none worked, resulting to either null values or same-as-first-time created intents, when the intent is retrieved on the BroadcastReceiver's side. Tactics used:
Flagging the intent that carries the object with:FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_CLEAR_TOP+FLAG_ACTIVITY_SINGLE_TOP
Result:Null values on the BroadacastReceiver's side
Flagging the pending intent created using the initial intent, with:FLAG_UPDATE_CURRENT or FLAG_CANCEL_CURRENT
Result:Null values on the BroadacastReceiver's side
Acquiring a random ID for intent or the pending intent using System.currentTimeMillis();
Result:Intents are not fired or received at all
Nothing described above. Result:Retrieving the same initial value every time.
Code for the calling method (stripped from any experimentations/producing null values):
private void setProximityAlert(MyCar myCar) {
String locService = Context.LOCATION_SERVICE;
LocationManager locationManager;
locationManager = (LocationManager)getSystemService(locService);
float radius = myCar.getMyCarRadius();
long expiration = myCar.getMyCarExpiration();
myService.setMyDriverLat(userLat);//setting user's position
myService.setMyDriverLng(userLng);//setting user's position
Intent intent = new Intent(myCar.getMyCarName());
intent.putExtra("myCar",myCar);
PendingIntent proximityIntent = PendingIntent.getBroadcast(this, -1, intent, 0);
locationManager.addProximityAlert(myCar.getMyCarLat(), myCar.getMyCarLng(), radius, expiration, proximityIntent);
}
Code for the calling method that sets the intent filter and registers the BroadcastReceiver:
public void addNewCarPoint (MyCar myCar){
IntentFilter filter = new IntentFilter(myCar.getMyCarName());
registerReceiver(new ProximityAlertReceiver(), filter);
setProximityAlert(myCar);
}
Code for the BroadcastReceiver's side:
public class ProximityAlertReceiver extends BroadcastReceiver {
#Override
public void onReceive (Context context, Intent intent) {
MyCar myCar=(MyCar)intent.getParcelableExtra("myCar");
driverLoc=(String)Double.toString(myCar.getMyDriverLat());
Toast.makeText(context, userLoc, Toast.LENGTH_SHORT).show();
Intent i = new Intent(context, MyCarDiscoveryPrompt.class);
context.startActivity(i);//firing intent
}
public void intentDataLoader(){
}
}
Any ideas would be more than welcome.
Thank you in advance.
Hmm i think i ve found something:
I placed the BroadcastReceiver (ProximityAlerReceiver), used to detect proximity alerts in the same class (MyCarTracking.class), where the LocationListener.class is located. This,
provides immediate access to fresh location updates, creating a new intent wrapped in a new pendingIntent to be fired to the BroadcastReceiver (only when the proximity criteria are met).
flags:FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_SINGLE_TOP and FLAG_CANCEL_CURRENT on intent and pendingIntent, were kept respectively. More specifically:
Code for LocationListener:
private final LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateWithNewLocation(location);//update application based on new location
}
public void onProviderDisabled(String provider){
updateWithNewLocation(null);//update application if provider disabled
}
public void onProviderEnabled(String provider){
// Update application if provider enabled
}
public void onStatusChanged(String provider, int status, Bundle extras){
//update application if provider hardware status changed
}
};
Code for setProximityAlert() method:
private void setProximityAlert() {
String locService = Context.LOCATION_SERVICE;
Context context =getApplicationContext();
LocationManager locationManager;
locationManager = (LocationManager)getSystemService(locService);
float radius = myCar.getMyCarRadius();
long expiration = myCar.getMyCarExpiration();
Intent intent = new Intent(CAR_DISCOVERED);
intent.putExtra("myCar",myCar);
locationManager.getLastKnownLocation(provider);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);//flagging intent
PendingIntent proximityIntent = PendingIntent.getBroadcast(context, -1, intent, PendingIntent.FLAG_CANCEL_CURRENT);//flagging pendingIntent
locationManager.addProximityAlert(myCar.getMyCarLat(), myCar.getMyCarLng(), radius, expiration, proximityIntent);//setting proximity alert
}
This solution works producing fresh intents with fresh location updates.
Thank you all for your help and your interest :)
Try adding
intent.setData(uri);
where uri is some unique value for each pending intent
I've been struggling with this problem as well. It took me a whole night to find that a weird bug I had was related to this issue.
Here's a good discussion on google code on the subject: http://groups.google.com/group/android-developers/browse_thread/thread/b2060b27c8934921
I've solved all my problems by (ab)using both the uri in SetData and the (reserved) request code in PendingEvent.GetWhatever.
I'm also using FLAG_CANCEL_CURRENT on my intents and making sure only pendingintents that share the same purpose get the same data, action and uri.
Hope it helps a little bit.
I create a proximity alert in this way
private void setProximityAlert(float radius, double lat, double lng, String place)
{
long expiration = -1;
LocationManager locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Intent intent = new Intent(TREASURE_PROXIMITY_ALERT);
intent.putExtra("lat", lat);
intent.putExtra("lng", lng);
intent.putExtra("place", place);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), -1, intent, 0);
locManager.addProximityAlert(lat, lng, radius, expiration, pendingIntent);
}
and on my activity I registered the receiver in this way
IntentFilter intentFilter = new IntentFilter(TREASURE_PROXIMITY_ALERT);
registerReceiver(new ProximityIntentReceiver(), intentFilter);
setProximityAlert(10, 45.150344, 9.999815, "POINT1");
and my broadcast receiver is correctly called.
So now, I want to add another proximity alert, is it possible? I want that the same boadcast receiver is called by 2 proximity alert.
I made this:
IntentFilter intentFilter1 = new IntentFilter(TREASURE_PROXIMITY_ALERT1);
registerReceiver(new ProximityIntentReceiver(), intentFilter1);
setProximityAlert(200f, 45.143848, 10.039741, "POINT2");
but it does not work, nothing happen. I'm really now on it and I was wondering if it is the right way. My intent is trigger 2 alerts, one when GPS get the position POINT1 and another one at the position POINT2.
Any helps are welcome.
You need to use whatever unique setAction so the system consider the two intents different, as otherwise will tend to reuse the first one.
I have this code:
Intent intent = new Intent(this,PlacesProximityHandlerService.class);
intent.setAction("foo"+objPlace.getId());
intent.putExtra(Poi._ID, objPlace.getId());
intent.putExtra(Poi.LAT, objPlace.getLat());
intent.putExtra(Poi.LON, objPlace.getLon());
PendingIntent sender = PendingIntent.getService(this,0, intent, 0);
LocationUtils.addProximity(this, objPlace.getLat(),objPlace.getLon(), objPlace.getError(), -1,sender);
Also note that the proximity alert works kinda tricky.
User enters the hot ZONE1 based on the signal precision and radius you set. Broadcast is fired for entering=true ZONE1. If you enter another zone ZONE2 that overlap with the current zone you don't get the alert as you are still in ZONE1.
You must leave the ZONE1, so the broadcast will fire again with entering=false. So once now you left ZONE1, if you arrive ZONE2 it will fire the broadcast entering=true ZONE2.
I've tested and it works just fine. Grab Location Spoofer free application from market and mock the location of the phone. You also need to enable mock locations in the phones Setting. And add additional permission to your application:
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
What I would do, set my location far away from me, probably Greenland, then set the position in a zone that triggers ZONE1, broadcast should fire. Then set again my location to Greeland, and set position that triggers ZONE2, broadcast should fire.
The entering flag can be get from the intent extras
Bundle b = intent.getExtras();
Boolean entering = (Boolean) b.get(android.location.LocationManager.KEY_PROXIMITY_ENTERING);
I used the above codes to setup proximity alerts for 100 POIs and all work well.