I have setup an alarm which is received by a BroadcastReceiver which launches a WakefulIntentService (class LocationMonitor). In the LocationMonitor I have :
private static final int MIN_TIME_BETWEEN_SCANS = 1 * 30 * 1000;
private static final int MIN_DISTANCE = 0;
#Override
protected void doWakefulWork(Intent intent) {
final CharSequence action = intent.getAction();
if (action == null) { // monitor command from the alarm manager
// the call below enables the LocationReceiver
BaseReceiver.enable(this, ENABLE, LocationReceiver.class);
if (lm == null) lm = (LocationManager) this
.getSystemService(Context.LOCATION_SERVICE);
Intent i = new Intent(this, LocationReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,
MIN_TIME_BETWEEN_SCANS, MIN_DISTANCE, pi);
} else if (ac_location_data.equals(action)) {
final Bundle extras = intent.getExtras();
if (extras != null) {
final Location loc = (Location) extras
.get(LocationManager.KEY_LOCATION_CHANGED);
if (loc == null) {
w("NULL LOCATION - EXTRAS : " + extras); //Log.w
// while gps is disabled I keep getting this :
// NULL LOCATION - EXTRAS : Bundle[{providerEnabled=false}]
} else {
final double lon = loc.getLongitude();
final double lat = loc.getLatitude();
w("latitude :" + lat + " -- longitude : " + lon);
}
}
}
}
I have several issues with the code above.
If GPS is initially disabled and then I enable it I get a bunch of W/GpsLocationProvider(...): Unneeded remove listener for uid 1000. The warning comes from here. I can't find in the code where is this removing of listeners triggered, nor can I see where they are assigned the uid 1000 (apparently the system server).
When I enable the gps I get the location as expected and then a "RemoteException"
LocationManagerService(...): RemoteException calling onLocationChanged on Receiver{4083ee68 Intent PendingIntent{4084e6b8: PendingIntentRecord{4083ef78 gr.uoa.di.monitoring.android broadcastIntent}}}mUpdateRecords: {gps=UpdateRecord{40838180 mProvider: gps mUid: 10064}}
which is not really a RemoteException, just a PendingIntent.CancelledException - the message is quite misleading. Or so I think : it comes from here which calls this. My question is : why is it reusing the Intent - shouldn't the FLAG_ONE_SHOT dispose of it ?
But the most important question is : when I register a PendingIntent like this what intents do I expect to receive ? And what flags should I use ?
Keep in mind I am using this pattern cause I want to have the phone update its position even when asleep and this achieves it (I do get the location updates). I try to simulate requestSingleUpdate (unavailable in 2.3) using FLAG_ONE_SHOT.
Receiver :
public final class LocationReceiver extends BaseReceiver {
private static final Class<? extends Monitor> MONITOR_CLASS =
LocationMonitor.class;
#Override
public void onReceive(Context context, Intent intent) {
d(intent.toString());
final String action = intent.getAction();
d(action + "");
final Intent i = new Intent(context, MONITOR_CLASS);
i.fillIn(intent, 0); // TODO do I need flags ?
i.setAction(ac_location_data.toString());
WakefulIntentService.sendWakefulWork(context, i);
}
}
To this question:
when I register a PendingIntent like this what intents do I expect to
receive ? And what flags should I use ?
When you register for location updates and pass a PendingIntent, this PendingIntent will be triggered when the LocationManager decides to inform you about a location update. You can provide pretty much whatever you want, depending on what you want to happen when the PendingIntent is triggered. The LocationManager will add an extra to the Intent that is sent. This extra has the bundle key LocationManager.KEY_LOCATION_CHANGED and the object associated with that key is a Location object.
LocationManager will use this PendingIntent again and again to inform your app of location updates, so I think using PendingIntent.FLAG_ONE_SHOT is probably not such a good idea. If you only want a single update, why don't you just unregister after you get one update?
EDIT: Add code to cancel any previously requested updates before registering for updates
Before you call registerLocationUpdates(), do this to cancel any previously registered updates:
Intent i = new Intent(this, LocationReceiver.class);
// Get any existing matching PendingIntent
PendingIntent pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_NO_CREATE);
if (pi != null) {
// Cancel any updates for this PendingIntent, because we are about to
// invalidate it
lm.removeUpdates(pi);
}
// Create a new PendingIntent and cancel any previous one
pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_CANCEL_CURRENT);
// Now register for location updates...
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,
MIN_TIME_BETWEEN_SCANS, MIN_DISTANCE, pi);
NOTE: Actually, I don't know why you need to cancel any previous PendingIntent and create a new one in this case. You can just get a PendingIntent and if you have already registered for location updates with that PendingIntent, I don't think that registering again will cause the PendingIntent to be used multiple times. If you want to try that, all you need to do is to remove PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT from your existing code. I think that is a better/cleaner/clearer solution.
Related
I'm having some trouble getting my proximity alert to work on my Android app that's running on an Emulator. Basically the proximity alert should start an activity that will (for now) print to the log, however when a desired location is set for the alert, and the emulator's location is set at that particular location, nothing happens. Here is the code for the proximity alert:
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Intent intent = new Intent(MY_PROXIMITY_ALERT);
PendingIntent proxIntent = PendingIntent.getActivity(MapActivity.this, 0, intent, 0);
lm.addProximityAlert(latlng.latitude, latlng.longitude, 100, -1, proxIntent);
Now MY_PROXIMITY_ALERT is declared in the manifest as stated below:
<receiver android:name=".myLocationReceiver">
<intent-filter>
<action android:name="PROXIMITY_ALERT"/>
</intent-filter>
</receiver>
And here is my code for myLocationReceiver
public class myLocationReceiver extends BroadcastReceiver{
private static final String TAG = "myLocationReceiver";
#Override
public void onReceive(Context context, Intent intent) {
final String key = LocationManager.KEY_PROXIMITY_ENTERING;
final Boolean entering = intent.getBooleanExtra(key, false);
if(entering) {
Log.d(TAG, "onReceive: Entering proximity of location");
}
}
}
I believe my problem has something to do with the Intent or PendingIntent object but I'm not entirely sure. Also I have heard that usually the GPS will take about a minute to actually register the proximity, but I still do not get a log message even after some time.
Thanks!
You've created an Intent with action MY_PROXIMITY_ALERT and then used PendingIntent.getActivity() to get a PendingIntent to pass to the LocationManager. When the proximity conditions are satisfied, LocationManager will try to start an Activity that is listening for action MY_PROXIMITY_ALERT.
Intent intent = new Intent(MY_PROXIMITY_ALERT);
PendingIntent proxIntent = PendingIntent.getActivity(MapActivity.this, 0, intent, 0);
In your manifest, you've declared a BroadcastReceiver that is listening for action MY_PROXIMITY_ALERT. This won't work.
Since you want the proximity alert to trigger a BroadcastReceiver, you need to get the PendingIntent like this:
Intent intent = new Intent(MY_PROXIMITY_ALERT);
PendingIntent proxIntent = PendingIntent.getBroadcast(MapActivity.this, 0, intent, 0);
Personally, I think it would be better to use an "explicit" Intent instead of an "implicit" Intent. In that case you would do it like this:
Intent intent = new Intent(MapActivity.this, myLocationReceiver.class);
PendingIntent proxIntent = PendingIntent.getBroadcast(MapActivity.this, 0, intent, 0);
You don't need to use the ACTION in the Intent.
Using an "explicit" Intent tells Android exactly what component (class) to launch. If you use an "implicit" Intent, Android has to search for components that advertise that they can handle certain ACTIONs.
How can I send a PendingIntent with a LocationResult?
I wanted to use same PendingIntent I setup for receiving location changes to also receive the last location.
See the last lines in the code:
// Create pending intent for a service which received location changes
Intent locationIntent = new Intent(getApplicationContext(), LocationIntentService.class);
PendingIntent pendingIntent = PendingIntent.getService(
getApplicationContext(),
0,
locationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
// Start listening to location changes
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, pendingIntent);
// Get last location
Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
// Send last location
if(lastLocation != null) {
// How to send it to the pending intent?
// Result result = LocationResult.create( ... )
// pendingIntent.send(result)
}
Inspecting the code for LocationResult.extractResult(intent) it is possible to see that LocationResult is nothing more than a Parcelable extra inserted in the Intent with a specific Id. The following Kotlin function inserts a List<Location> in a given Intent as a LocationResult
private const val EXTRA_LOCATION_RESULT = "com.google.android.gms.location.EXTRA_LOCATION_RESULT"
#VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun insertResult(intent: Intent, locations: List<Location>) {
val result = LocationResult.create(locations)
intent.putExtra(EXTRA_LOCATION_RESULT, result)
}
Note: FusedLocationApi has been deprecated and FusedLocationProviderClient should be used instead. I have applied the solution above to unit test the BroadcastReceiver receiving updates from FusedLocationProviderClient
I want to fake location in android. In android, there is a way for getting location using PendingIntent. Here is an example:
LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE);
String proximitys = "ACTION";
IntentFilter filter = new IntentFilter(proximitys);
LocationReceiver mReceiver = new LocationReceiver();
registerReceiver(mReceiver, filter);
Intent intent = new Intent(proximitys);
PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
service.requestLocationUpdates("network", 1000, 0.001f, proximityIntent);
And BroadcastReceiver will receive event when new location change:
public class LocationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Do this when the system sends the intent
Bundle b = intent.getExtras();
Location loc = (Location)b.get(android.location.LocationManager.KEY_LOCATION_CHANGED);
}
}
So, I want to hook this method. But I don't know how to hook this kind of method (that using PendingIntent). because PendingIntent will have data in "some future", and I don't know when it will happen. So hooking both before and after of method requestLocationUpdates seem not work because at that time, PendingIntent doesn't have any data yet.
Please tell me how to do this.
You need to intercept the broadcast receiver, not the pending intent. You can intercept LocationReceiver.onReceive and change the contents of the intent.
To do so intercept the onReceive by defining a beforeHookedMethod. Use its parameter (MethodHookParam params) to retrieve the intent (params.args[1]) and change its extras (intent.replaceExtras(bundle)).
Make sure your new bundle has the same key (android.location.LocationManager.KEY_LOCATION_CHANGED) and as value you can set your own location:
Location loc = new Location("yourprovider");
loc.setLatitude(66.6);
loc.setLongitude(66.6);
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.
this is my very first question:
I'm trying to configure proximity alerts that will be feed from a Database and webservice provider; but I have a problem configuring a simple proximity alert for testing. I manage to create the alert but it never gets fired, I'm only trying in the emulator for now and donĀ“t know if I need some extra code to trigger the alerts.
I've read somewhere that the GPS provider to disabled so the network provider can be used in order to trigger the alerts on the emulator.
My code looks like this:
Proximity intent declaration
private String proximityIntentAction = new String("gpsdelivery.gpship.getLocation.GPS_PROXIMITY_ALERT");
Inside onStart() where the parameters of the alerts are set
IntentFilter intentFilter = new IntentFilter(proximityIntentAction);
registerReceiver(new ProximityAlert(), intentFilter);
setProximityAlert(45.150344, 9.999815, -1);
Proximity alert function where the alerts get created
private void setProximityAlert(double lat, double lon, int requestCode)
{
// 100 meter radius
float radius = 100f;
// Expiration is 10 Minutes
long expiration = 600000;
LocationManager locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Intent intent = new Intent(proximityIntentAction);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
locManager.addProximityAlert(lat, lon, radius, expiration, pendingIntent);
}
And finally my proximity alert class with the Broadcast receiver
public class ProximityAlert extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.v("SomeTag","Proximity alert received");
}
}
Please let me know what I'm missing or what am I doing wrong. Thanks in advance, any source code would be appreciated.
Well, I believe your setProximityAlert(45.150344, 9.999815, -1); is incorrect. Specifically, your -1. It doesn't contain the information needed in the locManager.addProximityAlert(lat, lon, radius, expiration, pendingIntent);.