I have a problem with running a service from Alarm manager.
I am building an app that notifies the owner on the namedays of his facebook friends. It all works nicely, but the notification won't show up.
I've set up an AlarmTask that creates the PendingIntent and sets the AlarmManager, like this:
public void run() {
// Request to start are service when the alarm date is upon us
Intent intent = new Intent(context, NotifyService.class);
intent.putExtra(NotifyService.INTENT_NOTIFY, true);
intent.putExtra("notifyID", ID);
PendingIntent pendingIntent = PendingIntent.getService(context, ID, intent, 0);
// Sets an alarm - note this alarm will be lost if the phone is turned off and on again
am.set(AlarmManager.RTC_WAKEUP, date.getTimeInMillis(), pendingIntent);
}
The ID is specific for every nameday.
Now in my NotifyService, I have set up these:
#Override
public void onCreate() {
super.onCreate();
System.out.println("NOTIFICATION SERVICE onCreate()");
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
System.out.println("INTENT RECIEVED: " + intent + " " + flags + " " + startId);
// If this service was started by out AlarmTask intent then we want to show our notification
if(intent.getBooleanExtra(INTENT_NOTIFY, false)){
int ID = intent.getIntExtra("notifyID", -1);
showNotification(ID);
}
// We don't care if this service is stopped as we have already delivered our notification
return START_STICKY;
}
Both the methods are executed once when I start the app, but when the notification should come up, nothing happens.
Is there a way to test if the AlarmManager really executes the PendingIntent?
Should I rather use IntentService? Why/how?
Thanks a lot.
I tried to change it to BroadcastReciever, looking like this:
public class NotificationBroadcastReciever extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
System.out.println("BROADCAST RECIEVED");
}
}
The AlarmTask bit is changed to this:
Intent intent = new Intent("NotificationBroadcast");
intent.putExtra(NotifyService.INTENT_NOTIFY, true);
intent.putExtra("notifyID", ID);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), ID, intent, 0);
System.out.println("date for notification: " + date.get(Calendar.DAY_OF_MONTH) + "." + date.get(Calendar.MONTH) + "." + date.get(Calendar.YEAR));
System.out.println("epoch time in milils: " + date.getTimeInMillis());
// Sets an alarm - note this alarm will be lost if the phone is turned off and on again
am.set(AlarmManager.RTC_WAKEUP, date.getTimeInMillis(), pendingIntent);
and relevant manifest part looks like this:
<receiver
android:name="cz.cvut.kubispe2.jmeniny.NotificationBroadcastReciever"
android:exported="false">
<intent-filter>
<action android:name="NotificationBroadcast" />
</intent-filter>
</receiver>
I checked if the date that is to be set is equal to the epoch time and it is, but still, the onRecieve method is never called.
Both the methods are executed once when I start the app, but when the notification should come up, nothing happens.
_WAKEUP alarms are only guaranteed to wake up the device if they route to a BroadcastReceiver, not a Service. So long as what you are doing is very short (1-2 milliseconds), you can safely do that work in onReceive() of a BroadcastReceiver. The work you are presently doing in your Service would qualify.
Beyond that, use adb shell dumpsys alarm to confirm that your alarm is scheduled for when you think it is.
Should I rather use IntentService?
It would certainly be a better option than a regular Service, which you are leaking in your current implementation. However, the _WAKEUP limitation still holds, which is why I wrote WakefulIntentService, to help bridge the gap. Again, though, with the current limited work you are doing, just using a BroadcastReceiver should suffice.
try using application context.
PendingIntent pendingIntent = PendingIntent.getService(context.getApplicationContext(), ID, intent, 0);
And work with android logs. Then you will see if it's running in your console
Seems like I finally resolved it, I used the broadcast reciever, and found out where the error was - Calendar takes the month argument from 0 to 11, instead of 1-12, which I thought, since all the other arguments are dealt with normally. So I was just putting up a notification for the end of May, instead of today, when testing.
Anyway, thank you all for help, it was very appreciated.
Related
I have a problem on my app and I want to report this bug.
I develope the app which can crawls notifications using NotificationListenerService.
It works well.
But NotificationListenerService class has the problem I think.
Because, If the app is crashed, app can't crawl the notification at all,
UNTIL the phone reboots.
Is anyone who can solve this problem??
Please help me.
The bug is very clear!! But It is not easy to find the solution ....
If do you have already permissions then:
In your service class or another service/activity you can switch the "component hability" to listen notifications:
public void tryReconnectService() {
toggleNotificationListenerService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ComponentName componentName =
new ComponentName(getApplicationContext(), NotificationReaderV2Service.class);
//It say to Notification Manager RE-BIND your service to listen notifications again inmediatelly!
requestRebind(componentName);
}
}
/**
* Try deactivate/activate your component service
*/
private void toggleNotificationListenerService() {
PackageManager pm = getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(this, NotificationReaderV2Service.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(new ComponentName(this, NotificationReaderV2Service.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
Your notification listener, is a SERVICE, it can be killed by System, you can do your service as FOREGROUND to drastically decrease the probability that the system will kill your service.
#Override
public void onListenerConnected() {
super.onListenerConnected();
Log.d(TAG, "Service Reader Connected");
Notification not = createNotification();
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager != null) {
mNotificationManager.notify(NOTIFICATION_ID, not);
}
startForeground(NOTIFICATION_ID, not);
//Alarm to auto - send Intents to Service to reconnect, you can ommit next line.
alarmIt();
}
If do you like so more "safe", you can to programming not-friendly battery alarms, try to use inexact alarms please, the user's battery will be happy:
private void alarmIt() {
Log.d(TAG, "ALARM PROGRAMMATED at"+HotUtils.formatDate(new Date()));
Calendar now = Calendar.getInstance();
now.setTimeInMillis(System.currentTimeMillis());
now.set(Calendar.MINUTE, now.get(Calendar.MINUTE) + 1);
Intent intent = new Intent(this, NotificationReaderV2Service.class);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setAction(REBIND_ACTION);
PendingIntent pendingIntent = PendingIntent.getService(this, 0,
intent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
//The alarms that are repeated are inaccurate by default, use RTC_WAKE_UP at your convenience.
//Alarm will fire every minute, CHANGE THIS iF DO YOU CAN, you can't use less than 1 minute to repeating alarms.
manager.setRepeating(AlarmManager.RTC_WAKEUP, now.getTimeInMillis(), 1000 * 60 * 1, pendingIntent);
}
and next read the Intent to reconnect service binding:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Notification service onStartCommandCalled");
if (intent!=null && !HotUtils.isNullOrEmpty(intent.getAction()) && intent.getAction().equals(REBIND_ACTION)){
Log.d(TAG, "TRYING REBIND SERVICE at "+HotUtils.formatDate(new Date()));
tryReconnectService();//switch on/off component and rebind
}
//START_STICKY to order the system to restart your service as soon as possible when it was killed.
return START_STICKY;
}
Keep in mind that doing all these steps you can sure that your service will be killed anyway by the system but this code will restart the service and make it harder to kill it.
Maybe, you should consider using PARTIAL_WAKE_LOCK with your service and execute it in a process independently (:remote) if you want even more certainty (Maybe this is useless)
I would like to add a common error that is often followed, NEVER override the onBind and onUnbind method or overwrite the INTENT ACTION.
This will cause your service to not be connected and never run onListenerConnected
Keep the Intent as it is, in most cases you do not need to edit it.
I see exactly the same on this. The only "solution" I've found was to have the notification listener running in a separate process. Then if the rest of the app crashes it doesn't stop the listener. So it's only then specifically notification listener service crashes that require the reboot.
Seems a terrible and over complicated solution though.
I had the same problem. Here are few things that I did and now it works wonderfully for me.
Override onStartCommand, call super and return START_STICKY;
Override onNotificationRemoved, call super and add a toast so that you know in android itself that you service has not died yet whenever you swipe a notification.
Exclude your app from Battery saving list (Settings-> Battery-> Power Saving Exclusion)
Post this the service never dies even after the main app's crash. I dont need to reboot now to restart it.
I have been making an Android app lately..
In it I have used Pending Intent, with Alarm Manager.
I need to have multiple pending intents, and so I am using FLAG_ONE_SHOT. Alarm Manager will send broadcast at mentioned interval. And also, along with that I am using intent's setAction() method and passing currentTimeMillis() as Argument. And I have corresponding Broadcast Receiver. The problem is that once the app is closed, or deleted from the recents tray, the Broadcast receiver is not running.
The code is as follow:
setAlarm:
private void setupAlarm(int seconds) {
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(getBaseContext(), OnAlarmReceive.class);
//PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
intent.setAction(Long.toString(System.currentTimeMillis()));
intent.putExtra("id", ID);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ChatActivity.this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
Log.e(TAG, "Setup the Alarm");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, seconds);
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);}
Broadcast Receiver
public void onReceive(Context context, Intent intent) {
String id = intent.getStringExtra("id");
Log.e(TAG,"On the verge of deleting the message with id: "+id);
SQLiteDatabase database = context.openOrCreateDatabase("/sdcard/userlists.db", SQLiteDatabase.CREATE_IF_NECESSARY, null);
database.execSQL("DELETE FROM " + "MESSAGE" + " WHERE " + "id" + "= '" + id + "'");
broadcaster = LocalBroadcastManager.getInstance(context);
intent = new Intent(COPA_RESULT);
broadcaster.sendBroadcast(intent);}
Manifest.xml
<receiver android:name=".OnAlarmReceive" android:enabled="true" android:exported="true"/>
Please help me. I need the Broadcaster to do the job, even if the app is closed.
It's process life-cycle bug in which system may kill process when app goes in background to reclaim memory
You need to schedule JobService for receiving job whether application is active or not
from official document of Processes and Application Life Cycle
A common example of a process life-cycle bug is a BroadcastReceiver
that starts a thread when it receives an Intent in its
BroadcastReceiver.onReceive() method, and then returns from the
function. Once it returns, the system considers the BroadcastReceiver
to be no longer active, and thus, its hosting process no longer needed
(unless other application components are active in it). So, the system
may kill the process at any time to reclaim memory, and in doing so,
it terminates the spawned thread running in the process. The solution
to this problem is typically to schedule a JobService from the
BroadcastReceiver, so the system knows that there is still active work
being done in the process.
Here is example you can follow to complete your requirement
I've inherited a code base for an Android app and I'm facing a particularly though problem with local notifications.
The idea is to send a notification for each event which is scheduled in the future, considering also the reminder preference on how many minutes before the event the user wants to be notified.
Everything works just fine, except that after the notification is thrown for the first time, if the user opens the app before the event starts, the notification gets thrown another time. This happens every time the app is opened between (event start date - reminder) and event start date.
I've already gave a look at this and also this with no luck.
I've read that using a service may cause exactly this problem and some suggest to remove it but I think this is needed since the notification must be thrown also when the app is closed.
Currently the structure of the code is the following:
Edit - updated description of TabBarActivity
Inside TabBarActivity I have the method scheduleTravelNotification that schedules the AlarmManager.
This method is executed everytime there is a new event to be added on local database, or if an existing event have been updated.
The TabBarActivity runs this method inside the onCreate and onResume methods.
TabBarActivity is also the target of the notification - onclick event.
private static void scheduleTravelNotification(Context context, RouteItem routeItem) {
long currentTime = System.currentTimeMillis();
int alarmTimeBefore = routeItem.getAlarmTimeBefore();
long alarmTime = routeItem.getStartTime() - (alarmTimeBefore * 1000 * 60);
if(alarmTimeBefore < 0){
return;
}
if(alarmTime < currentTime){
return;
}
Intent actionOnClickIntent = new Intent(context, TravelNotificationReceiver.class);
PendingIntent travelServiceIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis(), actionOnClickIntent, PendingIntent.FLAG_ONE_SHOT);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(alarmTime);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), travelServiceIntent);
Log.e("NEXT ALARM", "Time: " + String.valueOf(calendar.getTimeInMillis()));
}
This is TravelNotificationReceiver.java (should I use LocalBroadcastReceiver instead of BroadcastReceiver?)
public class TravelNotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.e("RECEIVER", "received TravelNotification request");
Intent notificationIntent = new Intent(context, TravelNotificationService.class);
context.startService(notificationIntent);
}
}
TravelNotificationService.java extends NotificationService.java setting as type = "Travel", flags = 0, title = "something" and text = "something else".
public abstract class NotificationService extends Service {
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
sendNotification();
return super.onStartCommand(intent, flags, startId);
}
public abstract String setNotificationType();
public abstract int setNotificationFlags();
public abstract String setNotificationTitle();
public abstract String setNotificationText();
/**
* Executes all the logic to init the service, prepare and send the notification
*/
private void sendNotification() {
int flags = setNotificationFlags();
String type = setNotificationType();
NotificationHelper.logger(type, "Received request");
// Setup notification manager, intent and pending intent
NotificationManager manager = (NotificationManager) this.getApplicationContext().getSystemService(this.getApplicationContext().NOTIFICATION_SERVICE);
Intent intentAction = new Intent(this.getApplicationContext(), TabBarActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0, intentAction, flags);
// Prepares notification
String title = setNotificationTitle();
String text = setNotificationText();
Notification notification = NotificationHelper.buildNotification(getApplicationContext(), title, text, pendingIntent);
// Effectively send the notification
manager.notify(101, notification);
NotificationHelper.logger(type, "Notified");
}
}
Edit - Here's the code for NotificationHelper.buildNotification
public static Notification buildNotification(Context context, String title, String text, PendingIntent pendingIntent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setAutoCancel(true);
builder.setContentText(text);
builder.setContentTitle(title);
builder.setContentIntent(pendingIntent);
builder.setSmallIcon(R.mipmap.launcher);
builder.setCategory(Notification.CATEGORY_MESSAGE);
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
return builder.build();
}
Thank you for the answers!
Edit I've seen also this but has no accepted answers, while this post suggest something that I think it's already managed with if(alarmTime < currentTime){ return; } in scheduleTravelNotification.
This may not be your exact problem, but at a glance, you're sending the notification in onStartCommand() which can itself be run many times during the lifetime of the service -- for example, if you issue the service start command "blindly" in an onCreate of an activity, it will happen every time the activity is (re)created.
You have a few options for handling this.
One is to create a boolean flag as a property of the service, default to false, and check it before sending the notification. If it's false, send the notification and set it to true, and if it's already true you do not send a notification.
Another is to check and see if the service is already running, and if it is, don't send the service start command in the first place. This can be tedious to do everywhere, and violates DRY, so if you take this route you may want to create a static method in your service class which checks to see if the service is running and then starts it if not, and call that instead of explicitly starting the service.
Similar to user3137702 answer you could simple have a static boolean of APPISINFORGROUND which is checked everytime the send notification method is hit, and managed from your application/activities code.
As User said it is likely that your onStartCommand method is being called at odd times due to the app / service lifecycle.
Alternatively check your receiver is not being called somewhere else from your code.
It may be your NotificationHelper class which is causing an issue. Please share the code for this class.
One thought may be that your notification is not set to be auto cancelled, check if you include the setAutoCancel() method in your Notification Builder.
Notification notification = new Notification.Builder(this).setAutoCancel(true).build();
I've found a way to make it work, I'm posting this since it seems to be a problem of many people using the approach suggested in this and this articles. After months of testing I can say I'm pretty satisfied with the solution I've found.
The key is to avoid usage of Services and rely on AlarmScheduler and Receivers.
1) Register the receiver in your manifest by adding this line:
<receiver android:name="<your path to>.AlarmReceiver" />
2) In your activity or logic at some point you want to schedule a notification related to an object
private void scheduleNotification(MyObject myObject) {
// Cal object to fix notification time
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(myObject.getTime());
// Build intent and extras: pass id in case you need extra details in notification text
// AlarmReceiver.class will receive the pending intent at specified time and handle in proper way
Intent intent = new Intent(this, AlarmReceiver.class);
intent.putExtra("OBJECT_ID", myObject.getId());
// Schedule alarm
// Get alarmManager system service
AlarmManager alarmManager = (AlarmManager) getApplicationContext().getSystemService(getBaseContext().ALARM_SERVICE);
// Build pending intent (will trigger the alarm) passing the object id (must be int), and use PendingIntent.FLAG_UPDATE_CURRENT to replace existing intents with same id
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), myObject.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Finally schedule the alarm
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
}
3) Define AlarmReceiver
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Find object details by using objectId form intent extras (I use Realm but it can be your SQL db)
MyObject myObject = RealmManager.MyObjectDealer.getObjectById(intent.getStringExtra("OBJECT_ID"), context);
// Prepare notification title and text
String title = myObject.getSubject();
String text = myObject.getFullContent();
// Prepare notification intent
// HomeActivity is the class that will be opened when user clicks on notification
Intent intentAction = new Intent(context, HomeActivity.class);
// Same procedure for pendingNotification as in method of step2
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(context, myObject.getId(), intentAction, PendingIntent.FLAG_UPDATE_CURRENT);
// Send notification (I have a static method in NotificationHelper)
NotificationHelper.createAndSendNotification(context, title, text, pendingNotificationIntent);
}
}
4) Define NotificationHelper
public class NotificationHelper {
public static void createAndSendNotification(Context context, String title, String text, PendingIntent pendingNotificationIntent) {
// Get notification system service
NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
// Build notification defining each property like sound, icon and so on
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
notificationBuilder.setContentTitle(title);
notificationBuilder.setContentText(text);
notificationBuilder.setSmallIcon(R.drawable.ic_done);
notificationBuilder.setCategory(Notification.CATEGORY_MESSAGE);
notificationBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
notificationBuilder.setAutoCancel(true);
notificationBuilder.setContentIntent(pendingNotificationIntent);
notificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
notificationManager.notify(1001, notificationBuilder.build());
}
}
At this point it should work and schedule / trigger notification at the right time, and when notification is opened it will appear only once starting the activity declared in notification pending intent.
There is still a problem, AlarmManager have a "volatile" storage on user device, so if user reboots or switch off the phone you will lose all intents that you previously scheduled.
But fortunately there is also a solution for that:
5) Add at top of your manifest this uses permission
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
6) Right below the line added at step 1 register the boot receiver
<receiver android:name="<your path to>.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
7) Define the BootReceiver
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Do something very similar to AlarmReceiver but this time (at least in my case) since you have no source of intents loop through collection of items to understand if you need to schedule an alarm or not
// The code is pretty similar to step 3 but repeated in a loop
}
}
At this point your app should be able to schedule / trigger notification and restores those reminders even if the phone is switched off or rebooted.
Hope this solution will help someone!
I want to send my app to sleep and then wake it up at set times. I have it going to sleep but not waking up.
This sets the wakelock:
private void setWakeLock(){
System.out.println("wakelock");
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP
| PowerManager.ON_AFTER_RELEASE, "DoNotDimScreen");
wl.acquire();
}
This sets alarms for wake/sleep times:
private void setWakeSleep(){
java.util.Calendar c = java.util.Calendar.getInstance();
c.set(java.util.Calendar.HOUR_OF_DAY, 17);
c.set(java.util.Calendar.MINUTE, 53);
c.set(java.util.Calendar.MILLISECOND, 0);
Intent sleepIntent = new Intent("SLEEP_INTENT");
PendingIntent sleepPendingIntent = PendingIntent.getBroadcast(this, 0, sleepIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), sleepPendingIntent);
c.set(java.util.Calendar.HOUR_OF_DAY, 18);
c.set(java.util.Calendar.MINUTE, 14);
c.set(java.util.Calendar.MILLISECOND, 0);
Intent wakeIntent = new Intent("WAKE_INTENT");
PendingIntent wakePendingIntent = PendingIntent.getBroadcast(this, 0, wakeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager2 = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager2.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), wakePendingIntent);
}
And this is the broadcast receiver:
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Time updateHour = new Time();
updateHour.set(System.currentTimeMillis());
if (intent.getAction().equals("SLEEP_INTENT")) {
System.out.println("sleep");
wl.release();
}
if (intent.getAction().equals("WAKE_INTENT")) {
wl.acquire();
System.out.println("wake");
//initialise();
}
}
};
Any help greatly appreciated!
First, you don't want a wakelock; those are for keeping the device from going to sleep, which is highly anti-social unless your app really requires it (it kills the battery).
Second, your code to set the wakeup time will fail if you call it after 18:14 since you'll now be defining a time in the past. Let's ignore that for now.
Next, your intent action should be something like "org.user1797190.WAKE_INTENT" rather than simply "WAKE_INTENT" which could cause collisions. If you anticipate making this intent public, consider registering it at http://openintents.org. That's not your problem either, though.
You don't need alarmManager2 -- there's only one alarm manager in the system, so just re-use the first one.
I've never heard of making an app go to "sleep" per se. Do you mean you want the app to go away, and then come back later?
Here is what I would do. Forget about the "SLEEP_INTENT" completely. Just schedule a "WAKE_INTENT" and then call finish(). Your app will simply leave the screen.
I would forget about the broadcast receiver entirely. Instead, I would use getActivity() instead of getBroadcast() to get a pending intent that will restart the activity. Modify your manifest so that your WAKE_INTENT will go to the activity. Also, you should set the "android:launchMode" property to "singleTask" so multiple instances of your activity aren't created. You'll also need to implement onNewIntent() to handle the wakeup intent if your activity is already running when it arrives.
Finally, if your activity is part of the same application that will be creating the intent, you don't need a named intent at all; you can create them by class. You'll need another way to let the receiver know that this is a wakeup intent though.
So, putting it all together:
Your manifest should contain:
<activity android:name=".TestActivity" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Your code should contain:
/**
* Arrange for the activity to return at a specific time.
* Call finish() after calling this method().
* This function can be called from anywhere that has a valid Context.
*/
public static void scheduleWakeup(Context ctx, long timeMillis) {
if (DEBUG) Log.d(TAG, "Scheduling wakeup for " + timeMillis);
Intent intent = new Intent(ctx, TestActivity.class);
intent.putExtra("wakeup", true);
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
mgr.cancel(pi); // Cancel any previously-scheduled wakeups
mgr.set(AlarmManager.RTC_WAKEUP, timeMillis, pi);
}
...
protected void onCreate(Bundle state) {
Intent intent = getIntent();
if (intent.getBooleanExtra("wakeup", false)) {
// We were woken up by the alarm manager
}
...
}
protected void onNewIntent(Intent intent) {
if (intent.getBooleanExtra("wakeup", false)) {
// We were woken up by the alarm manager, but were already running
}
}
This is pretty close to what I'm doing in my own apps, and it works pretty well for me.
You'll have to test this yourself, of course. Log.d() is your friend.
as above. The problem was that I was using a broadcast receiver within the calling activity.
I've been struggling with this problem for days. I've also checked the documentation and several topics but didn't find any solution / explanation.
I am testing my application on LG p500 but I did a few test on Droid too and I get the same result.
My application uses AlarmHandler to schedule alarm. The application works correctly on the emulator and also on the device until the device has enough free memory.
When I start several other applications on the device and the memory is low the alarm will not fire anymore. As soon as I stop the "other" application the alarm works fine again.
Let me report the test and the result.
I set an alarm on my application 10 minute later.
I start several application (browser, google map, gmail, K9Mail,....)
I start the catlog to see the log of my application
Wait 15 minute without working on the phone
After 10 minutes the alarm should be fired but nothing happen until I wakeup my phone pressing a button
When I wake-up my phone the alarm immediatly fires and all the notificatin happen.
I stop the "other" application I previously started (browser, google map,...)
Set again an alarm 10 minute later
I start the catlog to see the log of my application
Wait without working on the phone
10 minutes later the alarm fires and I get notified.
I did this test several time and I get the same result.
Then I tried to set an alarm using the "Catch" application I previously downloaded from the market and I get the same behaviour so it looks like this is not a problem of my application.
Looking at the log of my application I do not see any error / exception but it looks like that when the system is low on memory something happen and the broadcast receiver does not start until the phone is waked up throught the keyboard. As soon as I wake-up the phone the receiver start and all the notification happen.
Here the code I used:
The Receiver:
public class NotificationReceiver extends BroadcastReceiver
{
public static final String LOG_TAG = "YAAS - Notification Receiver";
#Override
public void onReceive(Context context, Intent intent)
{
ScheduleActivityService.acquireStaticLock(context);
Log.i(LOG_TAG, "Received alarm - id: " + intent.getIntExtra("id", -1));
Intent intent2 = new Intent(context, ScheduleActivityService.class);
intent2.putExtra("id", intent.getIntExtra("id", -1));
context.startService(intent2);
}
}
The Service
public class ScheduleActivityService extends Service
{
public static final String LOCK_NAME_STATIC="it.hp.yaas.AppService.Static";
public static final String LOG_TAG = "YAAS - ActivityService";
private static PowerManager.WakeLock lockStatic = null;
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder
{
public ScheduleActivityService getService()
{
return ScheduleActivityService.this;
}
}
#Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
public static void acquireStaticLock(Context context) {
getLock(context).acquire();
}
synchronized private static PowerManager.WakeLock getLock(Context context)
{
if (lockStatic == null)
{
PowerManager mgr = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
lockStatic = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOCK_NAME_STATIC);
lockStatic.setReferenceCounted(true);
}
return(lockStatic);
}
/**
* This method is called when an alarm fires that is its alarm time is reached.
* The system assume that the alarm fired match the alarm time of the first
* activity.
* #param intent intent fired
* #param flag
* #param startId
*/
#Override
public int onStartCommand(Intent intent, int flag, int startId)
{
super.onStartCommand(intent, flag, startId);
try {
Log.i(LOG_TAG, "Alarm fired: " + startId + " - id: " + intent.getIntExtra("id", -1));
AlarmHandler.getInstance().onAlarmFired(intent.getIntExtra("id", -1));
}
finally { getLock(this).release(); }
return START_STICKY;
}
#Override
public void onDestroy()
{
super.onDestroy();
Log.i(LOG_TAG, "Destroy");
}
}
An piece of code from AlarmHandler, the routine called to schedule the alarm:
public synchronized void onAlarmFired(int alarmId)
{
scheduledAlarmId = -1;
Alarm alarmFired = pop();
if (alarmFired == null) return;
Log.i(LOG_TAG, "onAlarmFired (Alarm: " + alarmFired + ") at (time: " + Utilities.convertDate(new Date(), "HH:mm:ss") + ")");
notifyAlarmListener(alarmFired);
if (alarmFired.reschedule(null) != null) add(alarmFired);
Alarm alarm = peek();
if (alarm != null && scheduledAlarmId != alarm.getId()) scheduleEvent(alarm);
}
/**
* Schedule an alarm through AlarmManager that trigger next activity notification
* #param alarm alarm to be scheduled
*/
private void scheduleEvent(Alarm alarm)
{
Log.i(LOG_TAG, "scheduleEvent - (Alarm: " + alarm + ")");
Intent intent = new Intent(context, NotificationReceiver.class);
intent.putExtra("id", alarm.getId());
// In reality, you would want to have a static variable for the request code instead of 192837
PendingIntent sender = PendingIntent.getBroadcast(context, 192837, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Get the AlarmManager service
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, alarm.getTime().getTime(), sender);
scheduledAlarmId = alarm.getId();
}
And finally this is a piece of Manifest file:
<activity android:name=".ListActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".EditActivity"/>
<activity android:name=".SettingsActivity"/>
<service android:name="ScheduleActivityService"
android:label="YAAS Service"/>
<receiver android:name="NotificationReceiver" />
Are you sure your process doesn't get killed when you start all those applications? If it does, the alarms you set will die with it. It's not exactly clear who and when schedules the alarm in your code, but if it's the service, since it's sticky, it will eventually gets re-started, and you will get an alarm at some point (when you wake the device).
An easy way to check what alarms are registered at different points of your testing:
# adb shell dumpsys alarm
My code is very similar to yours on an alarm app that I wrote and use regularly. I haven't been able to reproduce the problem that you describe. I can't seem to get my phone to a state of extremely low memory. I opened every app I have installed and still have 260M free on my HTC Rezound.
As a safeguard in my app I used alarmmanager.setRepeating() instead of .set(). I set the repeat interval to 20 seconds. I passed the alarm ID as an intent extra just as you have. When my service starts it immediately cancels the pending intent using the alarm ID. My logic here is that if for any reason my alarm fails it will continue to try every 20 seconds until it succeeds.
In your code is AlarmManager.set(), which is not guaranteed to fire at the time you specify. It may fire 30 minutes or even 6 hours later, which I've seen happen on devices like the Xiaomi POCO F1.
Instead use AlarmManager.setExact() to schedule your code to run at a specific time.
Android 12 introduces an exact alarms permisison. If you don't want to deal with that, you can instead use AlarmManager.setWindow() with a small window like 15 minutes.