Android: How to handle a context within an AlarmService - android

In android (as a beginner) I have created an AlarmService class to act as a service, i.e. to run every fixed interval in the background, to check data and probably to notify the user that something is going on.
The AlarmService class is defined as follows:
public class AlarmService extends Service
{
Alarm alarm = new Alarm(this);
public void onCreate()
{
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
alarm.SetAlarm(this);
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startId)
{
alarm.SetAlarm(this);
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
}
and the actual Alarm is implemented as follows:
public class Alarm extends BroadcastReceiver
{
private Context mContext;
public final static String LOGHEAD = "StoxxAlarm";
public Alarm() {
mContext = null;
}
public Alarm(Context context) {
mContext = context;
}
#Override
public void onReceive(Context context, Intent intent)
{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
int updateInterval = Integer.parseInt(sharedPref.getString("updateInterval", "24")); // ##PROBLEM##
// ... do something here
}
public void SetAlarm(Context context)
{
// get the update cylce from the prefences
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
int updateInterval = Integer.parseInt(sharedPref.getString("updateInterval", "24"));
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, Alarm.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000 * 60 * 60 * updateInterval, pi); // Millisec * Second * Minute
}
public void CancelAlarm(Context context)
{
Intent intent = new Intent(context, Alarm.class);
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(sender);
}
}
The problem occurs in the line marked with ##PROBLEM## as the context is null. How can I fix the problem?
The funny thing is, that I fixed something else in the code which did not work. Before that fix (related to something else), this kind of worked. But now, how to make sure the function becomes a proper 'context' (what ever this is, I still do not understand properly what a context is...).

How can I fix the problem?
With respect to this specific concern, mContext will always be null, because Android will not use your one-parameter constructor when it sends you a broadcast. Get rid of mContext, and use the Context that is passed into onReceive().
Beyond that:
I do not know why you are returning START_STICKY
I do not know why you are overriding onStart() and onStartCommand(), given that onStart() was deprecated 6-7 years ago
I do not know why you have SetAlarm() and ClearAlarm() as methods on Alarm, forcing you to create an Alarm and then throw it away, instead of having those methods be implemented on the service, or have them be implemented as static methods somewhere
You are using RTC_WAKEUP, which is fine, but unless the work you plan to do is sub-millisecond in duration, you really need to use the WakefulBroadcastReceiver pattern, so you do not tie up the app's main application thread in onReceive() and also can keep the device awake

instead of mContext, you should use context

Related

Dynamically registered BroadcastReceiver instance not firing?

I've been trying to find the problem here, but I can't seem to... my onReceive doesn't seem to get called, here's what I have:
public abstract class NoUpdatesTimer extends BroadcastReceiver {
private Context context;
private PendingIntent pendingIntent;
public NoUpdatesTimer(Context context) {
this.context = context;
Intent intent = new Intent(Constants.ALARM_NO_LOC_UPDATES);
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
context.registerReceiver(this, new IntentFilter(Constants.ALARM_NO_LOC_UPDATES));
}
public void scheduleCheck(long delayMillis) {
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delayMillis, pendingIntent);
}
#Override
public void onReceive(Context context, Intent intent) {
...
}
}
With some debugging, I verified that scheduleChecking is called but the onReceive method is not called. I also tried to trigger this code from a shell, using:
adb shell am broadcast -a rsg.ms.7
(where Constants.ALARM_NO_LOC_UPDATES is "rsg.ms.7").
Can you tell me what to change so onReceive gets called?
Using the application context instead of the passed in service context seems to make it work, not sure why:
public abstract class NoUpdatesTimer extends BroadcastReceiver {
private Context context;
private PendingIntent pendingIntent;
public NoUpdatesTimer(Context context) {
this.context = context.getApplicationContext();
Intent intent = new Intent(Constants.ALARM_NO_LOC_UPDATES);
pendingIntent = PendingIntent.getBroadcast(this.context, 0, intent, 0);
this.context.registerReceiver(this, new IntentFilter(Constants.ALARM_NO_LOC_UPDATES));
}
public void scheduleCheck(long delayMillis) {
AlarmManager alarmManager = (AlarmManager)this.context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delayMillis, pendingIntent);
}
#Override
public void onReceive(Context context, Intent intent) {
...
}
}
The nature of PendingIntent is bound relatively to ApplicationContext.
for example its usage with Widgets where the broadcast is outside the application.
so when you're getting broadcast from PendingIntent in PendingIntent.getBroadcast you have to provide the application context not the lower layers.
I had the same issue once and it took me a whole day to figure this out.

Android - Stopping a periodic service properly

I would like to know if this is the proper way for stopping my periodically called service or not. I have a static boolean variable isServiceStopped in my MainActivity where I can start or stop the ongoing service by clicking on a button. As you see I reschedule my service in the onDestroy() method with AlarmManager, so if I called stopService() in my MainActvity it would just destroy the service but reschedule it again so there would not be any effect.
This is why I am using a flag, a static boolean variable that I can set in my MainActivity: if I click the button to stop the service I set this flag true and the service will not be rescheduled again as it will not run in the onDestroy() method.
This is working, but I personally believe that this is just a bad workaround since if I close the app how would the system find the static variable if only the service is running? So what is the proper way doing this? Should I put the reschedule part in the onStartCommand()?
Thanks!
public class AsyService extends Service {
#Override
public IBinder onBind(Intent i) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
//Right now I have a static variable defined in MainActivity to set the boolean isServiceStopped
if (!MainActivity.isServiceStopped){
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.set(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + (1000 * 15),
PendingIntent.getService(this, 0, new Intent(this, AsyService.class), 0)
);
}
}
}
MainAcvitiy:
btn2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 1, new Intent(MainActivity.this, AsyService.class), PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.cancel(pendingIntent);
}
});
The correct approach would be to recreate the original pending intent, cancel it, and remove the service from the Alarm Manager. I dont know why you would want to terminate the service from within itself.
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);

How to create a background notification

I want to implement a Service which permit to show a notification (daily, or after XX minutes) even if I close the application. (Actually, when I press back button, I finish the MainActivity...)
I need a runnable notification even if I didn't start the application (after rebooting the device for example, of course when the trigger is declanched)...
I tried some clear examples and tutorials but I doesn't find what I need.
Please HELP!
Thanks in advance,
Mohamed
You can do it with alarm manger:
public class AlarmHelper {
private Context context;
private AlarmManager alarmManager;
private static final String TAG = "AlarmHelper";
public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT";
public AlarmHelper(Context context) {
this.context = context;
alarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
}
public void setNotifyAlarm(Long milliSecond) {
PendingIntent pendingIntent = getNotifyPendingIntent();
alarmManager.cancel(pendingIntent);
alarmManager.set(AlarmManager.RTC_WAKEUP, milliSecond, pendingIntent);
// alarmManager.set(AlarmManager.RTC_WAKEUP, milliSecond, sender);
}
public void cancelNotifyAlarm() {
PendingIntent pendingIntent = getNotifyPendingIntent();
alarmManager.cancel(pendingIntent);
}
private PendingIntent getNotifyPendingIntent() {
Intent intent = new Intent(context, AlarmExpireService.class);
return PendingIntent.getService(context, 0, intent, 0);
}
public void setExpireAlarm(int minute) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, minute - 1);
PendingIntent pendingIntent = getExpirePendingIntent();
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
pendingIntent);
}
public void cancelExpireAlarm() {
PendingIntent pendingIntent = getExpirePendingIntent();
alarmManager.cancel(pendingIntent);
}
private PendingIntent getExpirePendingIntent() {
return PendingIntent.getService(context, 0, new Intent(context,
AlarmExpireService.class), 0);
}
}
Now in the AlarmExpireService.java:
public class AlarmExpireService extends Service {
// private static final String TAG = "AlarmExpireService";
private static final String TAG = "AlarmExpireService";
#Override
public void onCreate() {
super.onCreate();
//your logic for start activity or generate notification.
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
AppLog.Log(TAG, "On start command");
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
}
}
Now Register service in the manifest file:
<service android:name="your_package.AlarmExpireService" />
For just call a method from AlarmHelper's setNotifyAlarm method and your work is done pass the time in millisecond (it will start after that time and notification will be pop up).
For more information take a reference of this links:
1. AlarmManager
2. Service
3.Pending Intent

Android: Background IntentService not working

I'm attempting to start up a Background IntentService that will run every minute. I want to start this Service after the user logs into my app for the first time, so I have my code here:
This code will only be called after the user logs in:
public class MainMenuActivity extends SingleFragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
// This is reached after logging in, so we are setting alarm on login
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isOn = prefs.getBoolean(GPSTracker.PREF_IS_ALARM_ON, false);
GPSTracker.setServiceAlarm(this, true);// TODO: Always setting this to true, not sure if we should be
}
Then I have this code in my GPSTracker which extends IntentService:
private static final int POLL_INTERVAL = 1000 * 60 * 1;// 1 minute
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = new Intent(context, GPSTracker.class);
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if(isOn) {
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, System.currentTimeMillis(), POLL_INTERVAL, pi);// TODO: Is AlarmManager.ELAPSED_REALTIME what I want!?
} else {
alarmManager.cancel(pi);
pi.cancel();
}
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(GPSTracker.PREF_IS_ALARM_ON, isOn).commit();
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "In onHandleIntent");
}
However, my onHandleIntent is never called, and I never see anything in LogCat.
You are using AlarmManager.ELAPSED_REALTIME with System.currentTimeMillis(). That is an invalid combination.
Either:
Use AlarmManager.RTC with System.currentTimeMillis(), or
Use AlarmManager.ELAPSED_REALTIME with SystemClock.elapsedRealtime()

Cancelling an alarm that starts within a class that launches when the reboot is complete

Here is my class which launches and sets an alarm when the phone is rebooted.
public class NotifStart extends BroadcastReceiver
{
private static AlarmNotif reAlarm = new AlarmNotif();
#Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED"))
{
reAlarm.SetAlarm(context.getApplicationContext());
}
}
}
and here's my alarm's receiver class:
public class AlarmNotifReceiver extends BroadcastReceiver {
PendingIntent pi;
#Override
public void onReceive(Context context, Intent intent)
{
//stuff
}
public void SetAlarm(Context context)
{
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, AlarmNotifReceiver.class);
pi = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 10*1000, pi); // Millisec * Second
}
public void CancelAlarm(Context context)
{
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.cancel(pi);
}
}
Now, if I use SetAlarm() as the phone is open I am able to use CancelAlarm() and actually cancel it. But whenever I reboot the phone and set the alarm by using the above class NotifStart I am unable to cancel it. I tried to use the cancel method from within the NotifStart but since I can't really give the context of a non-activity class as onReceive it just doesn't work.
From the docs:
A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent). Once your code returns from this function, the system considers the object to be finished and no longer active.
So when you call your CancelAlarm your pending intent might be null or not equal to pending intent used for setting alarm.
In CancelAlarm(Context context) your pi might be null, check and re-create it with the same requestCode before performing cancel.
public void CancelAlarm(Context context)
{
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, AlarmNotifReceiver.class);
pi = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
}

Categories

Resources