I am writing unit tests for my app and i want to test, if the alarm is properly cancelled, but i can't find the right solution to do that. I am familiar with the method of checking if the alarm is active by using the PendingIntent.getBroadcast() with FLAG_NO_CREATE flag.
I managed to successfully use the getBroadcast() function repeatedly in same test by first checking if the alarm is not set at the launch of the app and then later to check if the alarm is set, both times returning the expected boolean values.
public void testTimerButtonStartProcess(){
Intent intent = new Intent (appContext, OnAlarmReceiver.class);
intent.putExtra(Scheduler.PERIOD_TYPE, 1);
intent.putExtra(Scheduler.DELAY_COUNT, 0);
intent.setAction(Scheduler.CUSTOM_INTENT_ALARM_PERIOD_END);
boolean alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertFalse("the alarm manager wasnt running when app is lauched", alarmUp);
solo.clickOnView(timerLayout);
instr.waitForIdleSync();
solo.sleep(5000);
alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertTrue("the alarm is set", alarmUp);
}
But when i try to do that in reverse order (first checking if the alarm is set and then later - if it is no longer set), my test failed, because after the second check (when the alarm should be cancelled) the getBroadcast() returned true (i expected to get false).
public void testTimerButtonLongPress(){
solo.clickOnView(timerLayout);
instr.waitForIdleSync();
Intent intent = new Intent (appContext, OnAlarmReceiver.class);
intent.putExtra(Scheduler.PERIOD_TYPE, 1);
intent.putExtra(Scheduler.DELAY_COUNT, 0);
intent.setAction(Scheduler.CUSTOM_INTENT_ALARM_PERIOD_END);
boolean alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertTrue("the alarm manager is running", alarmUp);
solo.clickLongOnView(timerLayout, 1500);
instr.waitForIdleSync();
solo.sleep(3000);
alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertFalse("the alarm manager was not running anymore", alarmUp);
}
I also tried to use the getBroadcast only once after the app cancelled the alarm, but i still got returned true.
Meanwhile i am assured, that the cancellation of the alarm works as expected, because app stops alarming after being "turned off".
Related
I am trying to implement activity transition updates in the background of my application. My goal is to request activity transition updates once and get activity transition updates all the time in the background. To achieve this, I implemented the following:
PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext, 100, intent,
PendingIntent.FLAG_NO_CREATE);
if (pi == null) {
pi = PendingIntent.getBroadcast(mApplicationContext, 100,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Task<Void> task = ActivityRecognition.getClient(mApplicationContext)
.requestActivityTransitionUpdates(atr, pi);
task.addOnCompleteListener(task1 -> {
if (task1.isSuccessful()) {
Log.v(TAG, "Travel behavior activity-transition-update set up");
} else {
Log.v(TAG, "Travel behavior activity-transition-update failed set up: " +
task1.getException().getMessage());
task1.getException().printStackTrace();
}
});
}
So, in this code if PendingIntent.getBroadcast(mApplicationContext, 100, intent,PendingIntent.FLAG_NO_CREATE); returns null, I call requestActivityTransitionUpdates method. Otherwise, I don't request, because the activity transition updates are already been requested with the pending intent.
However, this code doesn't work. After the first requestActivityTransitionUpdates, PendingIntent.getBroadcast(mApplicationContext, 100, intent, PendingIntent.FLAG_NO_CREATE) returns a pending intent and I don't call requestActivityTransitionUpdates and I stop getting transition updates.
If I use the following code by removing the PendingIntent.getBroadcast(mApplicationContext, 100, intent, PendingIntent.FLAG_NO_CREATE) line:
PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext, 100,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Task<Void> task = ActivityRecognition.getClient(mApplicationContext)
.requestActivityTransitionUpdates(atr, pi);
task.addOnCompleteListener(task1 -> {
if (task1.isSuccessful()) {
Log.v(TAG, "Travel behavior activity-transition-update set up");
} else {
Log.v(TAG, "Travel behavior activity-transition-update failed set up: " +
task1.getException().getMessage());
task1.getException().printStackTrace();
}
});
I keep getting the transition updates. But in this way, I have to request transition updates every time when the app opens. And after each transition update request, the API returns the current activity in my BroadcastReceiver class even though there is no actual activity transition happened.
So, is there a way to request transition updates once and keep getting transition updates all the time?
You can use the second way.
But as you say the broadcast receiver will be notified at each app run because the transition API returns the last detected activity.
You can get that event time and ignore it if it's an old one or if it's repeated by saving the last received one in some where such as db or shared preferences.
I'm trying to code a block of code that has to wake up a few times a day and notify a server. I'm trying to use an alarm intent and a broadcast receiver but the receiver is being rapidly triggered infinitely and I can't seem to stop this.
All of my code sits in one file. The process flow is simple.
Wake up on boot, check if we should communicate, attempt to communicate else set up one of two waiting conditions, activate alarm.
Wake up on alarm, attempt to communicate, re-activate alarm if necessary, otherwise kill it.
When I build and deploy this apk on my device the following process flow happens:
reboot
receiver receives boot intent just fine
alarm gets scheduled
alarm intent gets triggered after 80 seconds as intended
then after the next 80 seconds,
then log-cat shows the broadcast receiver being triggered very rapidly. Several times a second as if its being spammed.
I am completely baffled at why its behaving like this
private PendingIntent pendingIntent;
/**
* Receive a signal, in our case, the device has booted up
* #param context The Context in which the receiver is running.
* #param intent The Intent being received.
*/
#SuppressLint("UnsafeProtectedBroadcastReceiver")
#Override
public void onReceive(Context context, Intent intent) {
Log.d("autostart", "broadcast received");
if(intent.getAction()==null)return;
Intent alarmIntent = new Intent(context, autostart.class);
alarmIntent.setAction("device.activation.alarm");
pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);//cancel flag seems to be ignored
cancel(context);//cancel command seems to be ignored
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
// Set the alarm here.
Log.d("autostart","We have booted");
RegisterActivation registerActivation = new RegisterActivation(context);
if(registerActivation.AlreadyActivated())
return;//no need
if(registerActivation.ActivationPending()){
//perform regular activation
return;
}
if(registerActivation.canComplete()){
boolean success = registerActivation.sendActivation();//talk to server
if(success) {
registerActivation.markCompleted();
cancel(context);
}
else {
registerActivation.markFileWaiting();
startPending(context);
}
return;
}
if(registerActivation.shouldWait()){//if can complete fails, then shouldWait will immediately return true
Log.d("autostart", "waiting");
registerActivation.markFileSimWait();
startWait(context);
return;
}
}
if(intent.getAction().equals("device.activation.alarm")){
Log.d("autostart","alarm triggered");
cancel(context);
RegisterActivation registerActivation = new RegisterActivation(context);
if(registerActivation.AlreadyActivated()){//for now always false
cancel(context);
return;
}
if(registerActivation.ActivationPending()){//for now always false
//same as before
return;
}
if(registerActivation.canComplete()){//glitch happens here
if(registerActivation.sendActivation()){
registerActivation.markCompleted();
cancel(context);
}else{
registerActivation.markFileWaiting();
startPending(context);//this immediatly triggers the broadcast recieve
}
return;
}
if(registerActivation.shouldWait()){
registerActivation.markFileSimWait();
startWait(context);
}
}
}
public void startPending(Context context) {
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int interval = 80000;//will later become 4 hours
manager.set(AlarmManager.RTC_WAKEUP,interval,pendingIntent);
Log.d("autostart", "alarm activated");
}
public void startWait(Context context) {//same function but different time interval
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int interval = 90000;// will later become 12 hours
manager.set(AlarmManager.RTC_WAKEUP, interval, pendingIntent);
}
public void cancel(Context context) {
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.cancel(pendingIntent);
}
thank you to Mike M.
https://stackoverflow.com/users/2850651/mike-m
I'd mark your comment as the answer but you only commented
I went from
manager.set(AlarmManager.RTC_WAKEUP,interval,pendingIntent);
to:
manager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+interval, interval, pendingIntent);
I had to add the interval to the intervalMillis paramater.
And as tested, the manager.set(...) function is really not responding all too well to what I want it to do so I just have to trigger a cancel at the start.
Thanks again Mike :)
I am have been reading over this tutorial for creating repeating apps:
http://rdyonline.net/android-bytes-alarms/
I have followed the instructions and it is working beautifully. However I don't like using something if I don't understand how it works.
Alarm manager is using an version >= 19 so instead of alarm repeating (exact) it requires a one off alarm that is reset on exiting the intent.
Now as I said it is working, Every 15 minutes it is going off (in my version). I can see that they are bundling data with the intent, but I really have no understanding what is re-triggering the single shot alarm.
This is their code:
Repeating alarms
If you’re targeting any Android version before API 19 (KitKat), or,
you don’t need them to be exact then repeating alarms are nice and
easy. All you need in this case is to use the setRepeating call.
In some cases, it will be important that you set a repeating alarm
that is accurate, I’ll go in to a little more detail on how to handle
this.
The trick here is to make sure you schedule the next alarm once the
previous alarm goes off. You’ll have to check whether the alarm you
have set is intended to be repeated and also make sure the platform
you’re running on is above API 19
#Override
public void onReceive(android.content.Context context,
android.content.Intent intent) {
WrappedAlarmManager am = new WrappedAlarmManager(context);
Bundle extras = intent.getExtras();
if (am.isSingleAlarm(extras)) {
Toast.makeText(context, "Single alarm", Toast.LENGTH_SHORT).show();
} else if (am.isRepeatAlarm(extras)) {
Toast.makeText(context, "Repeat alarm", Toast.LENGTH_SHORT).show();
if (android.os.Build.VERSION.SDK_INT >= 19) {
am.scheduleRepeatingAlarm(context);
}
}
}
A quick check to see if it’s a repeating alarm and then the repeating
alarm is scheduled again. Below are the two pertinent methods to deal
with this logic:
public boolean isRepeatAlarm(Bundle extras) {
return extras.containsKey(KEY_REPEAT) && extras.getBoolean(KEY_REPEAT);
}
public void scheduleRepeatingAlarm(Context context) {
Intent intent = new Intent(context, NotificationReceiver.class);
Bundle extras = new Bundle();
extras.putBoolean(KEY_REPEAT, true);
intent.putExtras(extras);
PendingIntent pIntent = PendingIntent.getBroadcast(context,
REPEAT_ALARM_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Calendar futureDate = Calendar.getInstance();
futureDate.add(Calendar.SECOND, (int) (INTERVAL_SEVEN_SECONDS / 1000));
if (android.os.Build.VERSION.SDK_INT >= 19) {
setSingleExactAlarm(futureDate.getTime().getTime(), pIntent);
} else {
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, futureDate
.getTime().getTime(), INTERVAL_SEVEN_SECONDS, pIntent);
}
}
Thanks for your help
To schedule a repeating alarm in SDK versions >= 19, when alarm broadcast is received , the boolean value KEY_REPEAT is checked and if it is true then a single exact alarm is scheduled for INTERVAL_SEVEN_SECONDS / 1000 seconds later.
After INTERVAL_SEVEN_SECONDS / 1000 seconds later the broadcast is again received and next alram is set.
This receiving broadcast and scheduling next alarm cycle repeats continuously.
I have an application that uses alarms using AlarmManager. I set an alarm at a certain time and, when it triggers, it's set again 2 hours later. The second time the alarm is triggered it's not set again, so it's triggered just 2 times.
To know if the alarm triggered is the first one or the second I pass a boolean as a parameter to the BroadcastReceiver, like this:
Bundle b = new Bundle();
b.putBoolean(TODAlarm.KEY_IS_INFRACTION, true);
Intent intent = new Intent(me, TODAlarm.class);
intent.putExtras(b);
PendingIntent pendingIntent = PendingIntent.getBroadcast(me, TODAlarm.TYPE_DAILY_REST_NEEDED,
intent, PendingIntent.FLAG_ONE_SHOT);
alarmManager.set(AlarmManager.RTC_WAKEUP, time+2*HOURS, pendingIntent);
The first time I program the alarm I set KEY_IS_INFRACTION to false. When it's triggered I use the code from above and set it to true.
If I reinstall the application (using Eclipse->Run) when the alarm has been triggered once (I have an alarm programmed with KEY_IS_INFRACTION = true but it hasn't been triggered yet) the alarm programmed after the reinstall has KEY_IS_INFRACTION = false, but the intent received by the BroadcastReceiver has KEY_IS_INFRACTION = true (it seems like it receives the alarm programmed BEFORE reinstalling the application, even if I use alarmManager.cancel). This only happens when I reinstall the application, if I reboot the phone the intent received is ok. I always use alarmManager.cancel before alarmManager.set, but it's not working either.
The final application won't be reinstalled often, but if I publish an update this could lead to errors in the application behaviour. Anybody knows how can I fix it?
Here's the CORRECT sequence of actions (what the application should do):
1- Install aplication. Alarm is set to hour X with INFRACTION = false.
2- Hour X is reached and the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
3- Reinstall application. Alarm is set to hour X with INFRACTION = false. Since hour X has already passed, the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
Here's what it REALLY happens:
1- Install aplication. Alarm is set to hour X with INFRACTION = false.
2- Hour X is reached and the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
3- Reinstall application. Alarm is set to hour X with INFRACTION = false. Since hour X has already passed, the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = true and the alarm isn't set again.
[EDIT] Add some code. This is the function to set the alarm when the application is first started:
private void setNeededRestAlarmOrWarning(int type, long timeToStart){
Bundle b = new Bundle();
b.putInt(TODAlarm.KEY_ALARM_TYPE, type);
if(type == TODAlarm.TYPE_DAILY_REST_NEEDED){
b.putBoolean(TODAlarm.KEY_IS_INFRACTION, arr_bControls[CONTROL_PERMITIR_DORMIR_9_HORAS] ||
arr_iControls[CONTROL_LIMIT_TO_RECUPERATE] == 0 ||
arr_iControls[CONTROL_TIME_TO_RECUPERATE_COUNTER] >= Times.CTRL_PERMITTED_DESCANSOS_REDUCIDOS_POR_SEMANA);
Log.d(TAG, "INFRACTION IS "+b.getBoolean(TODAlarm.KEY_IS_INFRACTION));
}
Intent intent = new Intent(me, TODAlarm.class);
intent.putExtras(b);
PendingIntent pendingIntent = PendingIntent.getBroadcast(me, type,
intent, PendingIntent.FLAG_ONE_SHOT);
alarmManager.set(AlarmManager.RTC_WAKEUP, timeToStart, pendingIntent);
}
When the alarm is triggered I use the code posted above, before the EDIT.
[/EDIT]
I'm trying to check if my alarm is active or not. The alarmIsSet method will return false before the alarm is set, true when the alarm is set. So far so good, however, after the alarm i canceled alarmIsSet will continue to return true until I reboot the device.
How do I fix this?
public class Alarm extends Activity {
private Intent intent = new Intent("PROPOSE_A_TOAST");
private void alarm (boolean activate) {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
if(activate == true) {
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = 3000;
long triggerTime = SystemClock.elapsedRealtime();
am.setRepeating(type, triggerTime, interval, pi);
} else {
am.cancel(pi);
}
}
private boolean alarmIsSet() {
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}
}
You just have to add
pi.cancel();
after
am.cancel(pi);
After having some headaches with this stuff myself, I found out that if I somehow had created a pending intent while testing stuff, that it actually was not cleared between tests. Even killing the app didn't do it. The intent still stayed in the system and kept returning true when checking for it. I actually had to write some code to kill it before it tested right.
Easiest way is to check the values of the (date and) time in the alarm variable, if it is not the same value as when an alarm has not been set (for you to check once what that is) then it would indicate the alarm is active and at the time of the check in the program it is either a time that has passed and the alarm has sounded or it is a time that is yet to arrive and the alarm has not yet sounded or gone off. Note that the rules may permit only one alarm activation per device session before a reboot or power off or every 12 or 24 hours and that could be why the status is not cleared.