AlarmManager setExact with WakefulBroadcastReceiver sometimes not exact - android

Using Android 19+
setExact in conjuction with WakefulBroadcastReceiver sometimes does not fire on time (can be a few seconds or so late). I mean most it of the time it does. probably 49 times out of 50 its correct.
I'm not sure if its just because the system is busy at the time and it can't handle the workload or what
Here is how I set the alarm:
AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(AlarmReceiver.INTENT_FILTER);
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, MyApplication.ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmMgr.setExact(AlarmManager.RTC_WAKEUP, timeToWakeUp, alarmIntent);
Here is my receiver code:
public class AlarmReceiver extends WakefulBroadcastReceiver {
public static final String INTENT_FILTER = "myfilter";
#Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyWakefulService.class);
startWakefulService(context, service);
}
}
And in the WakefulService
public class MyWakefulService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
....

For Marshmallow era(?), we need some ugly codes like below... :(
And "delayInMillis" param should be more than 15 minutes on the API 23.
If not, system ignore the minutes less than 15 minutes.
private void registerExactAlarm(PendingIntent sender, long delayInMillis) {
final int SDK_INT = Build.VERSION.SDK_INT;
AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
long timeInMillis = (System.currentTimeMillis() + delayInMillis) / 1000 * 1000; //> example
if (SDK_INT < Build.VERSION_CODES.KITKAT) {
am.set(AlarmManager.RTC_WAKEUP, timeInMillis, sender);
}
else if (Build.VERSION_CODES.KITKAT <= SDK_INT && SDK_INT < Build.VERSION_CODES.M) {
am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, sender);
}
else if (SDK_INT >= Build.VERSION_CODES.M) {
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, sender);
}
}

This behaviour is added in API 19:
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
from AlarmManager.
Important: setExact() still does not have to be exact, as the docs state:
The alarm will be delivered as nearly as possible to the requested trigger time.

Related

Do action after some time when your app is not active / running

I have some sensitive data that I load on the device in my application. And it can be reused between sessions / multiple uses of the app and be cleared when not in use / open / active.
So the app is not active / running. There is a notification showing that the sensitive information is still in memory.
But I want to give the user an option of clearing this data after a set amount of time.
So my question is how do I run some code after a set number of minutes?
You can schedule an alarm using the AlarmManager and run your code from the BroadcastReceiver/Service. The alarm will trigger weather your app is dead or alive. (If it's dead it will be awaken)
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);
You can use this method to register a one time alarm. So if you want the alarm to trigger in 5 mins, the delayMillis value should be 5 * 1000 * 60
void registerOneTimeAlarm(PendingIntent pendingIntent, long delayMillis) {
int SDK_INT = Build.VERSION.SDK_INT;
long timeInMillis = System.currentTimeMillis() + delayMillis;
if (SDK_INT < Build.VERSION_CODES.KITKAT) {
alarmManager.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else if (SDK_INT < Build.VERSION_CODES.M) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
}
}
Let's say you will use a receiver, it will look like this:
public class AlarmReceiver
extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Your code here
}
}
And don't forget to add it in the Manifest:
<receiver android:name="your.package.AlarmReceiver"/>
IMPORTANT
Registered alarms are cleared when the device reboots, you need to re-register the alarm after the device boots. (If this is what you really need)
You can try this
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//Actions to be performed
}
}, TIME_OF_DELAY);

Android AlarmManager setExact() is not exact

I need to plan sheduled task every 10 minutes.
As in Lollipop and higher version setRepeating() is inexact, I use setExact() and (on alarm firing) I set new exact alarm in 10 minutes.
private void setAlarm(long triggerTime, PendingIntent pendingIntent) {
int ALARM_TYPE = AlarmManager.ELAPSED_REALTIME_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(ALARM_TYPE, triggerTime, pendingIntent);
} else {
alarmManager.set(ALARM_TYPE, triggerTime, pendingIntent);
}
}
triggerTime is calculated SystemClock.elapsedRealtime() + 600_000;
When alarm fires, firstly I plan new one, only after that I run my sheduled task.
setAlarm();
mySheduledTask;
I do have WAKE_LOCK permission in my manifest.
When I test this on Android 4 - it works perfect (deviation might be 12-15 milliseconds).
But when I run app on Xiaomi Redmi Note 3 Pro (5.1.1) - deviation can be up to 15 seconds!
For example, I see in my log file: first run was at 1467119934477 (of RTC time), second - at 1467120541683. Difference is 607_206 milliseconds, not 600_000, as it was planned!
What am I missing? What is a way to simulate behaviour of system alarm (it's the most close usecase that can describe my tack)?
PS. I use IntentService for PendingIntent = PendingIntent.getService(context, 0, myIntent, 0);
The OS chooses how the alarms will work, with consideration of the time you've specified. Because of that, when the phone gets into a 'semi-sleep' mode, it won't necessary use the resource at the time you wish it to. Basically, it waits for 'windows' that the OS opens for it, and only then the alarm you want to run will run, that's why you're experiencing time gaps.
This was introduced on Marshmallow OS and will continue on Nougat OS as well, as part of Google trying to improve the device's battery.
Here's the thing, you have 2 options:
Accept the time delays (but maybe consider using JobScheduler which is more recommended and will save you battery).
Use setExactAndAllowWhileIdle which might cause you battery issues (use this carefully, too many alarms will be bad for your battery).
This method isn't repeating, so you have to declare the next job to be run at the service which the pendingIntent opens.
If you choose option 2, here's the start:
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int ALARM_TYPE = AlarmManager.RTC_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
am.setExactAndAllowWhileIdle(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
am.setExact(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else
am.set(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
You can call the method from support.v4:
AlarmManagerCompat.setExact(...);
The internal implementation contains checks by sdk version.
Probably a possible workaround could be something like this:
you schedule the Alarm about 1 minute before the expected time, than you use a Handler.postDelayed to cover the remaining time.
Here you can find an example of this kind of implementation.
The activity just set-up the first alarm:
public class MainActivity extends AppCompatActivity {
private static int WAIT_TIME = 60*1000; //1 minute
public static int DELAY_TIME = 10*60*1000; // delay between iterations: 10min
public static String UPDATE_TIME_KEY = "update_time_key";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setAlarm(this,(new Date().getTime())+DELAY_TIME);
}
public static void setAlarm(Context context, long delay) {
long fireDelay = delay-WAIT_TIME;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
sharedPreferences.edit().putLong(UPDATE_TIME_KEY,delay).apply();
Intent startIntent = new Intent(context, UpdateReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, startIntent,PendingIntent.FLAG_UPDATE_CURRENT );
AlarmManager alarmManager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
int ALARM_TYPE = AlarmManager.RTC;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(ALARM_TYPE, fireDelay, pendingIntent);
} else {
alarmManager.set(ALARM_TYPE, fireDelay, pendingIntent);
}
}
}
than the receiver continues the loop:
public class UpdateReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, Intent intent) {
Log.e("RECEIVED","RECEIVED");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
long fireTime = sharedPreferences.getLong(MainActivity.UPDATE_TIME_KEY, (new Date()).getTime());
long fireDelay =(fireTime-(new Date().getTime())>0)?fireTime-(new Date().getTime()):0;
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
Log.e("RECEIVED","PERFORMED");
MainActivity.setAlarm(context,(new Date()).getTime()+MainActivity.DELAY_TIME);
}
},fireDelay);
}
}
I hope it helped.
To answer the question on the system alarm...
Android's stock Alarm Clock/Desk Clock app uses a combination of setAlarmClock and setExactAndAllowWhileIdle.
The following code is used to update notifications:
final PendingIntent operation = PendingIntent.getBroadcast(context, 0,
AlarmStateManager.createIndicatorIntent(context), flags);
final AlarmClockInfo info = new AlarmClockInfo(alarmTime, viewIntent);
alarmManager.setAlarmClock(info, operation);
While at the same time the following code is used to schedule the actual alarm:
if (Utils.isMOrLater()) {
// Ensure the alarm fires even if the device is dozing.
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent)
}
The Pending intent set in setExactAndAllowWhileIdle triggers the alarm while setAlarmClock's intent is then simply ignored.
Android Googlesource
From android documentation of AlarmManager
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
Also while using setExact() :
The alarm will be delivered as nearly as possible to the requested trigger time.
So its still not guaranteed that setExact will be Exact.
You can try use AlarmManager.setAlarmClock maybe it can help you.
Another thing you need to check which type of BroadcastReceiver you are using, it will be better to use WakefulBroadcastReceiver
Btw you need to change logic for work with Alarm Manager for support Android M, you can you something like this:
if(Build.VERSION.SDK_INT < 23){
if(Build.VERSION.SDK_INT >= 19) {
setExact(...);
} else {
set(...);
}
} else {
setExactAndAllowWhileIdle(...);
}

On android 4.4.2 ,the AlarmManager service don't work correctly

I'm using AlarmManager as below,but it is not working on KITKAT.
The BroadCastReceiver doesn't receive the action
first step: get the PendingIntent:
long now = SystemClock.elapsedRealtime();
Intent wakeIntent = new Intent(AlarmDetectService.this, GlobalBroadcastAction.class);
wakeIntent.setAction(Constant.TCP_KEEP_ALIVE_TIMER);
wakeIntent.putExtra(Constant.TARGET_SERVER,server_Address);
alivePi = PendingIntent.getBroadcast(
AlarmDetectService.this, requestCode, wakeIntent,
PendingIntent.FLAG_UPDATE_CURRENT);//request_code is server_address's hashCode.
second step:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.w(TAG, "Build.VERSION_CODES.KITKAT");
aliveAm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
(long) (now + keepAliveTime * 1000 * 0.8),
(PendingIntent) alivePi);
} else {
aliveAm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
(long) (now + keepAliveTime * 1000 * 0.8),
(PendingIntent) alivePi);
}
This behaviour is added in API 19:
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will
shift alarms in order to minimize wakeups and battery use. There are
new APIs to support applications which need strict delivery
guarantees; see setWindow(int, long, long, PendingIntent) and
setExact(int, long, PendingIntent). Applications whose
targetSdkVersion is earlier than API 19 will continue to see the
previous behavior in which all alarms are delivered exactly when
requested.
from AlarmManager.
Important:
setExact() still does not have to be exact, as the docs state:
The alarm will be delivered as nearly as possible to the requested
trigger time.
Try to write separate methods:
private void setAlarm(AlarmManager am, long interval, PendingIntent pi){
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + interval, pi);
}
#TargetApi(Build.VERSION_CODES.KITKAT)
private void setAlarmToKitkat(AlarmManager am, long interval, PendingIntent pi){
am.setWindow(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + interval, 20000, pi);
}
and when you call it:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
{
setAlarm(am, interval, pi);
}
else
{
setAlarmToKitkat(am, interval, pi);
}

Alarm Manager doesn't trigger on time

I am trying to create app where i need to send location updates every 1,5,10 minutes and so on.
When app is running, it work accurately but when it goes into background/sleep mode it doesn't work accurately.
I tried both the methods setRepeating/setInExactRepeating but none of them work in background mode.
public static void startSensorAlaram(Context ctx, long minutes) {
AlarmManager alarmManager = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
// Alarm_Receiver is a broadcast receiver.
Intent intent = new Intent(ctx, Alaram_Receiver.class);
intent.setAction(Utility.SENSOR_ACTION);
PendingIntent pi = PendingIntent.getBroadcast(ctx, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),minutes,pi);
// alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), minutes, pi);
}
public static void stopAlaramSensor(Context ctx) {
Intent intent = new Intent(ctx, Alaram_Receiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 1,
intent, 0);
AlarmManager alarmManager = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
}
Alarm Receiver - Broadcast receiver
public class Alaram_Receiver extends WakefulBroadcastReceiver {
private SharedPreferences sp;
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
sp = context.getSharedPreferences(Utility.SHARED_PREFS, 0);
if (intent.getAction().equalsIgnoreCase(Utility.SENSOR_ACTION)) {
if (sp.getBoolean("logged_in", false)) {
// context.startService(new Intent(context,SensorService.class));
startWakefulService(context,new Intent(context,SensorService.class));
} else
Utility.stopAlaramSensor(context);
}
}
}
Note:- Min API version is 15 and compile version is 23.
There are two issues.
1) As of Android API >= 19, you should use new AlarmManager.setExact() method instead of set() or setRepeating(). Here is the quote from official document.
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will
shift alarms in order to minimize wakeups and battery use. There are
new APIs to support applications which need strict delivery
guarantees; see setWindow(int, long, long, PendingIntent) and
setExact(int, long, PendingIntent).
2) As of Android 6.0, there is a deep sleep mode introduced, called Doze.
It is designed to reduce battery consumption when device is being standby. There are so many restriction and what you could do in that mode is very limited. You need to use the new AlarmManager.setExactAndAllowWhileIdle() to make Alarm fired in Doze mode at your preferred time.
More information about Doze mode is available here Optimizing for Doze and App Standby

AlarmManager is not repeating at the expected time

I want to start a service every a predefined time.
From my main activity I call SetAlarm method of a WakefulBroadcastReceiver with this code:
public class MyReceiver extends WakefulBroadcastReceiver {
public static final String PREFS = "Prefs";
SharedPreferences mSettings;
#Override
public void onReceive(Context context, Intent intent) {
Log.i("Receiver", "START");
Intent service = new Intent(context, Awservice.class);
startWakefulService(context, service);
}
public void SetAlarm(Context context) {
String Hour = getDuration(context, "PREFS_HOURS","0");
String Min = getDuration(context, "PREFS_MINUTES","0");
Long LHour = Long.parseLong(Hour);
Long LMinutes = Long.parseLong(Min);
Long time = (LHour*60+LMinutes)*60*1000;
AlarmManager am=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+time, time , pi);
}
public void CancelAlarm(Context context) {
Intent intent = new Intent(context, MyReceiver.class);
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(sender);
}
String getDuration(Context context, String value, String defaultValue) {
SharedPreferences mSettings = context.getSharedPreferences(PREFS, Activity.MODE_PRIVATE);
return mSettings.getString(value, defaultValue).toString();
}
The problem is that my code works but is inaccurate.
e.g. If I set the activation time every 2 minutes the broadcast receiver doesn't work every 2 minutes but as follows:
21:32:52.068
21:33:27.842
21:36:38.258
21:37:59.437
21:39:42.178
21:41:30.643
21:43:27.898
21:45:42.199
What is wrong? Thanks in advance!
On projects that have android:targetSdkVersion set to 19 the setRepeating() will not be exact. From the docs:
Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS
will shift alarms in order to minimize wakeups and battery use. There
are new APIs to support applications which need strict delivery
guarantees; see setWindow(int, long, long, PendingIntent) and
setExact(int, long, PendingIntent). Applications whose
targetSdkVersion is earlier than API 19 will continue to see the
previous behavior in which all alarms are delivered exactly when
requested.
From CommonsWare's Busy Coders guide:
The only way to get exact repeating would be to use setExact() and to
re-schedule the event yourself, rather than relying upon Android doing
that for you automatically. Ideally, you use setInexactRepeating(), to
help extend battery life.

Categories

Resources