I have implemented a BroadcastReceiver (called ProximityIntentReceiver) in my Android app, but i have some annoying problems. In the onCreate() method of my MainActivity I call this function:
addProximityAlert():
private ProximityIntentReceiver proximityIntentReceiver;
private void addProximityAlert() {
pendingIntentList = new HashMap<Integer, PendingIntent>();
double latitude = 37.567072;
double longitude = 14.273046;
Intent intent = new Intent(PROX_ALERT_INTENT);
PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
locationManager.addProximityAlert(
latitude, // the latitude of the central point of the alert region
longitude, // the longitude of the central point of the alert region
POINT_RADIUS, // the radius of the central point of the alert region, in meters
PROX_ALERT_EXPIRATION, // time for this proximity alert, in milliseconds, or -1 to indicate no expiration
proximityIntent // will be used to generate an Intent to fire when entry to or exit from the alert region is detected
);
pendingIntentList.put(1, proximityIntent);
proximityIntentReceiver = new ProximityIntentReceiver();
registerReceiver(proximityIntentReceiver, filter);
Toast.makeText(getApplicationContext(),"Alert Added",Toast.LENGTH_SHORT).show();
}
This is, instead, the onDestroy of my MainActivity:
onDestroy():
#Override
public void onDestroy() {
super.onDestroy();
accessTokenTracker.stopTracking();
if(proximityIntentReceiver!=null) {
unregisterReceiver(proximityIntentReceiver);
removeProximityAlert();
}
}
removeProximityAlert():
private HashMap<Integer, PendingIntent> pendingIntentList;
private void removeProximityAlert () {
Iterator<Map.Entry<Integer, PendingIntent>> iterator = pendingIntentList.entrySet().iterator() ;
while(iterator.hasNext()){
Map.Entry<Integer, PendingIntent> pendingIntentMap = iterator.next();
locationManager.removeProximityAlert(pendingIntentMap.getValue());
}
}
Now if i change something on my BroadcastReceiver or in my addProximityAlert(), changes remain the same until I reboot the device. Why? I thought that by calling the unregisterReceiver() method was sufficient to remove the instance of the BroadcastReceiver...
What should I add to my code?
A BroadcastReceiver only exists during the execution of its onReceive() method. So, this evaluates to a throw-away instance every time the broadcast is fired/received. For dynamically registering/unregistering of BroadcastReceivers, you have to remember the instance of your receiver in onPause() to register it again during onResume().
#Override
protected void onPause() {
unregisterReceiver(mReceiver);
super.onPause();
}
For an Activity:
In order to register your BroadcastReceiver from within your app, first, remove the <receiver> tag from your AndroidManifest.xml file. Then, call registerReceiver(BroadcastReceiver receiver, IntentFilter filter) in your onResume(). Use unregisterReceiver(BroadcastReceiver receiver) in your onPause() to unregister the BroadcastReceiver.
For a Service:
Remove the receiver tag from the manifest file. You can then register your BroadcastReceiver with the same method in the onCreate() and unregister in the onDestroy().
Took Reference From: 1: Unregister broadcast receiver 2: unregister receiver
You can check here, which gives you more details about the working of BroadcastReceivers.
Still not your problem is solved, please let me know.
I suspect your constraints (POINT_RADIUS etc) are not being updated because you're retrieving your pending intent with flags '0':
PendingIntent.getBroadcast(this, 0, intent, 0);
LocationManager.addProximityAlert() takes the pending intent you give it and sets up a recurring alarm using the intent it contains. If you subsequently call addProximityAlert() with a PendingIntent that matches the previous one (according to Intent.filterEquals()), the alarm won't get updated, and your new constraints will not take effect.
To fix, you need to set the right flag so that the pending intent replaces the previous one.
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
The flags are documented here (incidentally, I think '0' is undefined behaviour): http://developer.android.com/reference/android/app/PendingIntent.html
NB, if FLAG_UPDATE_CURRENT does not work, you may need FLAG_CANCEL_CURRENT instead.
Related
I need to periodically read data from Google Fit via the Fitness API for a watch face development.
I'm using AlarmManager every POLL_INTERVAL_MS interval to broadcast an Intent to a WakefulBroadcastReceiver which starts an IntentService executing the data read task.
This is the code I'm using.
In the manifest file
<receiver android:name=".FitDataAlarmReceiver"></receiver>
In watch face engine onCreate
FitDataAlarmReceiver mFitDataAlarmReceiver = new FitDataAlarmReceiver();
mFitDataAlarmReceiver.setAlarm(MyApp.this);
In FitDataAlarmReceiver class
public class FitDataAlarmReceiver extends WakefulBroadcastReceiver {
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
private static final long FIT_DATA_POLL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(60);
public void setAlarm(Context context) {
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, FitDataAlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 123456, intent, 0);
alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, POLL_INTERVAL_MS, POLL_INTERVAL_MS, alarmIntent);
}
public void cancelAlarm(Context context) {
if (alarmMgr!= null) {
alarmMgr.cancel(alarmIntent);
}
}
#Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, FitDataManagerService.class);
startWakefulService(context, service);
}
}
In my IntentService I then obtain a new wake lock, start the data read stuff and then release it together with the receiver one (by calling completeWakefulIntent method).
cancelAlarm method is of course called in the watch face onDestroy.
mFitDataAlarmReceiver.cancelAlarm(MyApp.this);
It works. But, what's happening is that the onReceive in the FitDataAlarmReceiver class is triggered twice very quickly without even respecting the first POLL_INTERVAL_MS parameter of the setRepeating method call.
It takes a couple of - literally - false alarms before the receiver starts receiving correctly timed invocations. I don't know why.
SetAlarm is called only once in the onCreate method of the watch face engine.
However the onCreate method of the watch face engine itself is also (inexplicably) being called two times(!).
I've filtered out the second onCreate call by using a simple semaphore, and it is now called only once.
Nevertheless, onReceive is still called twice.
Only if I use the debugger and place a breakpoint in the onReceive, it is called just once.
These are my questions:
Why is the onCreate method in the watch face Engine class called two times?
And, even if I manage to filter out one call, why is the receiver onReceive being called twice?
I have started to implement the Google Location API using this tutorial.
I've managed to get it to work in my application quite fine, it updates my location at the right intervals etc. Now I am working on how to update my location when the device is in sleep mode. According to the documentation, this method is the way to go:
public void requestLocationUpdates (LocationRequest request, PendingIntent callbackIntent);
My question is, how do I set up this PendingIntent, and how do I handle it? I've seen tutorials of how to handle other types of intent, but I am not sure how to apply them to this.
You can either register Broardcast Reciever or Activity through pending intent.Sample Example for registering boardcast reciever:
String proximitys = "ACTION";
IntentFilter filter = new IntentFilter(proximitys);
registerReceiver(mybroadcast, filter);
Intent intent = new Intent(proximitys);
PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
locationManager.requestLocationUpdates(provider, mintime, mindistance,
proximityIntent);
Your Broardcast Reciever:
public class ProximityIntentReceiver extends BroadcastReceiver {
#SuppressWarnings("deprecation")
#Override
public void onReceive(Context arg0, Intent intent) {
//action to be performed
}
I am currently working on a map app that has points of interest built into it.
These points are supposed to be announced to the user by means of a proximity alert trigger.
Here is the addproximityAlert() code that I'm using
loc.addProximityAlert(lat, longe, radius, -1, PendingIntent.getActivity(
c, 0, new Intent().putExtra(loc_name, loc_name), flag));
The idea is that once the alert fires an alert dialog pops up with a short blurb about the site with the option to either close the alert or get more info(uses WebView).
Thus far I have no run-time or compile-time errors but as I approach each site, nothing happens.
My theory on why nothing happens is that either;
1) I haven't used the PendingIntent correctly, or
2) I haven't set up the BroadcastReceiver correctly
Here is the XML code for the BroadcastRecevier,
<receiver android:name=".ProxyAlertReceiver" >
<intent-filter>
<action android:name="entering" />
</intent-filter>
</receiver>
My current plan to fix this issue is to modify the PendingIntent to use a new Intent like this;
...new Intent(myContext, ProxyAlertReceiver.class)...
and see if I get any results.
Opinions and advice on my issue would be greatly appreciated!
Have you tried PendingIntent.getBroadcast(...)?
Intent locationReachedIntent = new Intent("entering");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1234,
locationReachedIntent, 0);
locationManager.addProximityAlert(longitude, latitude, radius, -1, pendingIntent);
I have the above code working in my application.
Use This
Intent locationIntent = new Intent();
Bundle extras= new Bundle();
extras.putString("loc_name",loc_name);
locationIntent.putExtras(extras);
PendingIntent pendingIntent= new PendingIntent.getActivity(this,0,locationIntent,0);
loc.addProximityAlert(lat, longe, radius, -1, pendingIntent, flag));
I assume your loc_name is a string. This will work.
Implementing a proximity alert depends on more than just calling the addProximity method on a Location Manager.
You must also:
Create a receiver class which will fire when alert is triggered and will receive a status (entering or exiting) and the action name*;
public class ProximityReceiver extends BroadcastReceiver {
public String TAG ="ProxReceiver";
#Override
public void onReceive(Context context, Intent intent) {
/* your code here - sample below */
final String key = LocationManager.KEY_PROXIMITY_ENTERING;
final Boolean entering = intent.getBooleanExtra(key, false);
if (entering) {
Toast.makeText(context, "LocationReminderReceiver entering", Toast.LENGTH_SHORT).show();
Log.v(TAG, "Poi entering");
} else {
Toast.makeText(context, "LocationReminderReceiver exiting", Toast.LENGTH_SHORT).show();
Log.v(TAG, "Poi exiting");
}
Log.v(TAG,"Poi receive intent["+intent.toString()+"]");
Bundle extras = intent.getExtras();
// debugging only
int counterExtras = extras.size();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(TAG, "Prox Poi extra "+String.format("key[%s] value[%s] class[%s] count[%s]", key,
value.toString(), value.getClass().getName(), String.valueOf(counterExtras)));
}
} else {
Log.v(TAG, "Prox Poi extra empty");
}
}
}
Declare this receiver in your Manifest file;
<receiver android:name=".ProximityReceiver" >
<intent-filter>
<action android:name="my" />
</intent-filter>
</receiver>
Register (associate your pending intents to) this receiver, adding proximity alert(s). Only register your receiver ONCE in your code. If one registers a receiver multiple times, it will fire once for every receiver instance (you reach a POI, which registers a pending intent called "my". **
// create proximity alert
Intent locationIntent = new Intent("my");
ProximityReceiver proximityReceiver = new ProximityReceiver();
PendingIntent pendingIntent = PendingIntent.getBroadcast(mapView.getContext(), <some identifying text>,
locationIntent, 0);
loc.addProximityAlert(lat, longe, radius, -1, PendingIntent.getActivity(
c, 0, new Intent().putExtra(loc_name, loc_name), flag));
IntentFilter filter = new IntentFilter("my");
context.registerReceiver(proximityReceiver, filter);
Where context can be this if running in same activity.
Unless you want to keep on receiving alerts even when in background (or even terminated), you must implement removal and re-creation of proximity alerts in your onPause and onResume methods, like this SO question (jump to the end of the question).
note * In this example, "my" will be the action name (see Intent declaration) for an action and will be passed along with the intent AND a bundle of extras containing, at least, the key entering (LocationManager.KEY_PROXIMITY_ENTERING) with boolean value, which gives you the state of the alert, if one is entering (1) or exiting (0) the proximity radius.
note ** If you have registered the receiver for "my" multiple times, it will fire multiple times for every proximity alert event that calls an intent named "my".
I'd like the following behavior:
The user clicks a notification and Android stops my Service.
The problem is that stopping a Service requires a call to stopService and I cannot easily create a PendingIntent that does that.
So the only way I found to do this is to have my Service receive a special Intent extra that causes the Service to call stopSelf and stop.
Is there a simpler way to directly cancel a Service from a notification click?
Thanks CommonsWare.
Here is a quick illustration of your solution for those who are interested.
Code is in the service class.
// Create Notification
private void initNotification() {
//Register a receiver to stop Service
registerReceiver(stopServiceReceiver, new IntentFilter("myFilter"));
PendingIntent contentIntent = PendingIntent.getBroadcast(this, 0, new Intent("myFilter"), PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(context, contentTitle, contentText,contentIntent);
mNotificationManager.notify(NOTIFICATION_ID,notification);
...
}
//We need to declare the receiver with onReceive function as below
protected BroadcastReceiver stopServiceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
stopSelf();
}
};
You could create a simple BroadcastReceiver that does the stopService() call, and use a getBroadcast() PendingIntent to trigger it. That BroadcastReceiver could be registered in the manifest or via registerReceiver() by the Service itself (in the latter case, it would do stopSelf() rather than stopService()).
That's probably not any simpler than what you have, though, and there is no way to directly trigger a stopService() call from a PendingIntent.
I have a activity A, it register AlarmManager to trigger another BroadcastReceiver B. When time is reached, onReceive() of B will be called, and start another activity C. A may be closed when C is started.
My problem is:
- C don't know the pendingIntent in A, how can I call alarmManager.cancel(pendingIntent) in C?
- Or, how can I pass pendingIntent from A to B to C?
Pls help.
In my application I created a static method that returned the PendingIntent required for the AlarmManager, and then I can call it from any class. If you have a PendingIntent that doesn't change between times it is called this can work for you. For example, I have:
public static PendingIntent getSyncPendingIntent(Context context)
{
Intent i = new Intent(context, <classname>.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
return pi;
}
And I can just call that from any class to get the same PendingIntent.
It would be far easier to manage the Alarm and its intent in a singleton service than to try to pass it from activity to activity, and far less brittle (you could introduce Activity D somewhere in the middle without having to daisy chain the intent further).
You can register a broadcast receiver in A to listen for a custom action that is broadcasted when C is started
In Activity A
private BroadcastReceiver onActivityCStartedReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//cancel the pendingIntent for the alarm here
}
};
register the receiver
registerReceiver(onActivityCStartedReceiver , new IntentFilter(ACTIVITY_C_STARTED_ACTION));
In Activity C call
Intent i = new Intent(ACTIVITY_C_STARTED_ACTION);
context.sendBroadcast(i);
try it! make use of the messaging system for your good :D
To cancel/destroy all the services which you have generated, then generally you need the same "pendingInetent" and "AlarmManager" variable which you have used for starting those services,
for example if your previous variable is am_mngr and pndngInt then use it like this in your stopservice method.
am_mngr.cancel(pndngInt); // this will cancel the previous servicse...