Below is a pretty simple method that takes a Date and an id for an alarm to be fired which starts a countdown. For some reason I don't understand, if I call it once with one date and id 0 and call it again with another date and id 1 (i.e., two different countdowns), Android will fire both alarms at the same time (specifically the first date passed with id 0) so both countdowns start at the same time.
Can anyone tell me why and how to fix it? Thanks!
public void setCountdownAlarm(Date fireTime, int id)
{
// startCountdown will be called at fireTime
BroadcastReceiver startCountdown = new BroadcastReceiver() {
#Override public void onReceive( Context context, Intent theIntent )
{
countdownTimer = new Timer();
countdownTimer.schedule(new TimerTask() {
#Override
public void run() {
onSecondTick(showtime);
}
}, 0, 100); // call every 10th of a second
}
};
this.registerReceiver( startCountdown, new IntentFilter("com.counter.app.CountActivity.COUNT") );
Intent intent = new Intent("com.counter.app.CountActivity.COUNT");
PendingIntent pintent = PendingIntent.getBroadcast( this, id, intent, 0 );
AlarmManager manager = (AlarmManager)(this.getSystemService( Context.ALARM_SERVICE ));
if (Build.VERSION.SDK_INT >= 19)
manager.setExact(AlarmManager.RTC_WAKEUP, fireTime.getTime(), pintent);
else
manager.set(AlarmManager.RTC_WAKEUP, fireTime.getTime(), pintent);
}
Editing to say that when I waited for the second alarm to fire, Android actually calls startCountdown twice -- once again for each alarm. Help!
I figured out what I did wrong. As explained in answers to similar questions, the second parameter of PendingIntent.getBroadcast (requestCode) must be unique if you want to get a unique pending intent. I took care of that by passing "id".
The second problem was that I registered the BroadcastReceiver each time I called setCountdownAlarm. The BroadcastReceiver should only be registered once, typically in the onCreate method of the activity.
Related
I'm creating my first Android Notifications app, so I'm very much a beginner. I have a class, Notification.java, that asks the user for the time and date. Using these data, it creates an alarm that is triggered at the specified date and time.
Here is my code for Notification.java
public class Notification extends Activity {
private PendingIntent pendingIntent;
private SetAlarm alarm;
private Date date;
private Time time;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alarm);
findViewById(R.id.setTime).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setAlarmTime();
}
});
findViewById(R.id.setDate).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setAlarmDate();
}
});
findViewById(R.id.checkBox).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
createAlarm();
}
});
}
private void setAlarmTime() {
}
private void setAlarmDate() {
}
private void createAlarm() {
alarm = new SetAlarm();
}
}
The createAlarm() method is supposed to actually create the alarm using the information that the user has provided (i.e. time and date). However, I understand that I need the following code block to create the alarm?
private void setTheAlarm() {
Intent alarmIntent = new Intent(SetAlarm.this, AlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast(SetAlarm.this, 0, alarmIntent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
int interval;
/* Set the alarm to the date specified by user */
/* Repeating on every x minutes interval */
}
However, the Notification.java is where I am extending 'Activity'. It is also where I have the 'pendingIntent; code.
So essentially, how can I move the alarm creation code into a separate class when the code dealing with the Activity is in an entirely different class?
Thanks for the help. I hope my question is clear enough.
Not exactly clear if that is what you want, but if I understand you correct, you need the alarmManager inside an extra class to reach it from everywhere? You could make a static one like this:
public class MyAlarmManager{
private static AlarmManager mAlarmManager;
private static PendingIntent mPendingIntent;
//start alarm
public static void setAlarm(Context context, int alarmId, long alarmTime) {
if (mAlarmManager== null) {
mAlarmManager= (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
Intent startAlarmIntent = new Intent(context, YouReceiver.class);
if(mPendingIntent==null){
mPendingIntent = PendingIntent.getBroadcast(context, alarmId,
startAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
//check the version because of doze mode since MM
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
alarmTime, mPendingIntent);
} else {
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mPendingIntent);
}
}
//stop alarm
public static void stopAlarm(Context context, int id) {
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
Intent stopAlarmIntent = new Intent(context, YourReceiver.class);
if(mPendingIntent==null){
mPendingIntent= PendingIntent.getBroadcast(context, id, stopAlarmIntent, 0);
}
mAlarmManager.cancel(mPendingIntent);
mPendingIntent.cancel();
}
}
Then you can call it like:
MyAlarmManager.setAlarm(this, id, interval);
and stop it:
MyAlarmManager.stopAlarm(this, id);
You can do this from every class by passing the context and the identical id . The alarm id must be the same as you passed by starting the alarm, otherwise it will not work. Notice that above MarshMallow, there are some changes for AlarmManager and it´s possible that it does not work in every case. If your app get´s killed or goes into idle mode, the alarm won´t be triggered in every circumstance. To handle doze mode, see this:https://developer.android.com/training/monitoring-device-state/doze-standby.html
And be aware of any third party app and battery managers, that could kill your app. Also, Huawei devices have their own battery management besides the doze mode.
If this is not what you wanted, come back. Can´t guarantee that there is no error because I have overseen something, it´s from scratch.
I´ve got a method that sets the time I want an alarm to fire.
In this method I also got a Stop button that cancels the alarm
if(alarmManager != null){
alarmManager.cancel(pi);
}
My problem is that when I set the alarm, go out of the app and in again to cancel the alarm I get a nullPointer. Im guessing it is because the PendingIntent also closes when I leave the app (get set to null).
How can I prevent this from happening, so that I can cancel the alarm?
Heres the whole method:
#SuppressWarnings("deprecation")
public void setTime(){
Calendar mcurrentTime = Calendar.getInstance();
int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
int minute = mcurrentTime.get(Calendar.MINUTE);
TimePickerDialog mTimePicker;
mTimePicker = new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener()
{
int callCount = 0; //To track number of calls to onTimeSet()
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute)
{
if(callCount == 1) // On second call
{
String timeString = "";
timeString = selectedHour + ":" + selectedMinute + ":00";
Log.d("TEST", "Chosen time : "+ timeString);
setAlarm(timePicker, selectedHour, selectedMinute);
}
callCount++; // Incrementing call count.
}
}, hour, minute, true);
mTimePicker.setButton(DialogInterface.BUTTON_POSITIVE, "Set", mTimePicker);
mTimePicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Stop", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if(alarmManager != null){
alarmManager.cancel(pi);
}
}
});
mTimePicker.setTitle(R.string.time);
mTimePicker.show();
}
You can save the data that is used to construct your PendingIntent in a Bundle like shown here
So when you open up your app again, you will be able to reconstruct the PendingIntent and cancel the alarm if needed.
EDIT: First of all you have to override onSaveInstanceState and save the data that is used to create your PendingIntent object like shown below:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the pending intent id
savedInstanceState.putInt(PENDING_INTENT_ID, mPendingIntentId);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
After that, in your onCreate method you will check if the savedInstanceState is not null. If not null, restore the items from the savedInstanceState (Bundle) object like this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mPendingIntentId = savedInstanceState.getInt(PENDING_INTENT_ID);
} else {
// Probably initialize members with default values for a new instance
mPendingIntentId = YOUR_DEFAULT_INT_VALUE;
}
mPendingIntent = PendingIntent.getBroadcast(this, mPendingIntentId, YOUR_INTENT, PendindIntent.FLAG_UPDATE_CURRENT);
}
PendingIntent will be recreated every time you will enter your app, so you will be able to cancel the same alarm that has been triggered in a previous step.
EDIT 2 : Another approach in your case would be that since you have only one alarm, and it's id is always the same (which should be used for the PendingIntent's requestId), than you can simply recreate the PendingIntent whenever you want to add or cancel an alarm. A method like the one below would help you out.
private PendingIntent getPendingIntent(Context context){
return PendingIntent.getBroadcast(context, 0, new Intent(context, YourBroadcastReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT);
}
I am trying to schedule an alarm to run even when the app is in the background. The code below runs fine if the app is in the foreground. However, as soon as I press the back button I get a "leaked Intent Receiver error" suggesting that I am missing a call to unregisterReceiver(). I tried adding unregisterReceiver(receiver) to onStop() and onPause(), but continue to get the same error. Any help is much appreciated!
Also a side question, if I do finally figure out how to unregister the receiver, will that prevent my alarm from triggering? Thanks.
// Alarm
public void SetAlarm()
{
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override public void onReceive( Context context, Intent _ )
{
// code to run when alarm hits
context.unregisterReceiver( this ); // this == BroadcastReceiver, not Activity
}
};
this.registerReceiver( receiver, new IntentFilter("com.blah.blah.somemessage") );
PendingIntent pintent = PendingIntent.getBroadcast( this, 0, new Intent("com.blah.blah.somemessage"), 0 );
AlarmManager manager = (AlarmManager)(this.getSystemService( Context.ALARM_SERVICE ));
// set alarm to fire 5 sec (1000*5) from now (SystemClock.elapsedRealtime())
manager.set( AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000*10, pintent );
}
I have this method called "createAlarms()" which is on initial app setup, sets an alarm for a specific time. On the running of this alarm, it creates a notification which I am creating using a Broadcast Receiver Class.
public void createAlarms() {
cal = Calendar.getInstance();
cal.add(Calendar.HOUR, alarmintervalint);
calintent = new Intent(this, AlarmBroadcastReceiver.class);
calpendingintent = PendingIntent.getBroadcast(this.getApplicationContext(), 12345, calintent, 0);
am = (AlarmManager)getSystemService(Activity.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, alarmintervalint, calpendingintent);
}
I want this alarm to repeat every "alarmintervalint" time period. I could do this by using the "am.setRepeating()" function, but my problem is a bit more complicated then that. After sending a certain amount of alarms (such as 50. will be calculated by the program), I want all the values to change, so that the alarmintervalint will change.
private void showNotification(Context context) {
PendingIntent notifpi = PendingIntent.getActivity(context, 0, new Intent(context, Main.class), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Hello!")
.setContentText("Welcome!")
.addAction(R.drawable.ic_launcher, "Open App", notifpi);
mBuilder.setContentIntent(notifpi);
mBuilder.setDefaults(Notification.DEFAULT_SOUND);
mBuilder.setAutoCancel(true);
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(1, mBuilder.build());
System.out.println("6");
}
For example, right now, an alarm goes off every 2 hours, and I get a notification every 2 hours. After getting 50 notifications, I want to set the Alarm to go off after 3 hours. And again after 50 notifications, make it 4 hours. (This is just an example, it will be a bit more complicated.
How would I do this? Right now what I think is to have some sort of counter in my broadcastreceiver class, and after the counter reaches 50 (in this example's case), it will call upon the createAlarms() class and change the timings and stuff. Would this work?
Simplest way is to use the Timer. Set TimerTask in your initial setup method, main method or whatever.
Example :
import java.util.TimerTask;
public class Schedular extends TimerTask {
#Override
public void run() {
//your implementation.
System.out.println("Run Me ~");
}
}
import java.util.Timer;
import java.util.TimerTask;
public class Main{
public static void main(String[] args){
TimerTask task = new Schedular();
Timer timer = new Timer();
//miliseconds
timer.schedule(task, 1000, 60000);
}
}
For Ref : Timer
I'm not totally sure what you're using the alarm for, but it looks like you want people to gradually smoke less often.
In that case I'd like to add to the previous answers, that you might want to persist your last used interval in case your app crashes or the phone is rebooted. You can easily archieve that by using SharedPreferences.
So I've got 3 java files :
ServiceActivity - main activity where everything starts ( static int i is defined earlier in this file)
Elserwis - it is the service (it has a timer where I've passed the variable i -> it will be the hour since when the timer must turn on)
Sekundo - the intent where user puts the hour => >variable i<
Here is the fragment of code from main activity -> ServiceActivity:
private OnClickListener startListener = new OnClickListener() {
public void onClick(View v){
Intent intent = new Intent(getApplicationContext(), Sekundo.class);
startActivityForResult(intent,1337);
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data ) {
/* Place out code to react on Activity-Result here. */
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1337){
i=data.getIntExtra("result",5);
Toast tost = Toast.makeText(getApplicationContext(), "ELO"+data.getIntExtra("result",0)+data.getIntExtra("result1",0), 1000);
tost.show();
startService(new Intent(SerwisActivity.this,Elserwis.class));
}
}
I think the problem is in the end, where startService lays (as a subfunction of onActivityResult)
If you need any other fragment of code I can paste it here, but the question is:
My app is running very slowly at the beginning when the timer starts, and the Toast shows for over 1 minute. Anyone know why?
EDIT:
public class Elserwis extends Service {
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate() {
super.onCreate();
okresowePowiadomienie();
Toast.makeText(getApplicationContext(),"Service LAUNCHED!", 1000).show();
}
Date data33 = new Date(111,11,SerwisActivity.i,2,25);
int d = data33.getDate();
Timer timer = new Timer();
public void okresowePowiadomienie(){
TimerTask timerTask = new TimerTask(){
public void run() {
NotificationManager notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.ic_launcher,"OKRes",System.currentTimeMillis());
Intent notIntent = new Intent(getApplicationContext(), SerwisActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notIntent, 0);
notification.setLatestEventInfo(getApplicationContext(),"Powiadomienie x:","Kliknij aby d:usunac ;)t:"+d,contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(1335, notification);
// } };
}};
timer.scheduleAtFixedRate(timerTask ,data33 , 120000); }
#Override
public void onDestroy() {
super.onDestroy();
timer.cancel();
Toast.makeText(this, "Service dead!", Toast.LENGTH_LONG).show();
}
}
thats just alpha version of my code but final will be similar ( now it only passes information about the day to my service -> in final version it should pass hour and minute)
The "Service LAUNCHED!" toast stays on for ages, it crashes most of the time on AVD, on my real smartphone it just takes long but still it should work smoothly...
Basically the problem started when i moved startService from onClick() function TO the onActivityResult. It needs to stay there because service uses the int i (user types types int i in the new intent) to set the data for my timer(timer is in the Elserwis). I've updated my first post with the service code so u can get what i mean
I'm GUESSING that startService itself is not causing any blocking.
I'm GUESSING that you have code in startService that takes awhile to complete and causes your application UI to lock up.
If this is the case, then what you would need to do is create a new thread inside your service before running the code that causes the delay.
You need to keep any long-running blocks of code in a separate thread to not block the UI. I would be interested in the code that is in Elserwis.class because that would help identify where the problem actually lies. Or if you look at your code and figure it out based on what I said, then you need not post any more code.