How can I send scheduled notifications in Android Studio?
I got this task: I want to get notifications at the some chosen time of the day every day. I can easily get them when app is alive, but when its closed notifications don't come up.
I've already tried JobScheduler, AlarmManager and WorkManager and none of these didn't work well.
My project runs at minimum SDK 26 (Android Oreo). Target SDK is 30. Last code version looks like that:
AlertReceiver.java
public class AlertReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
intent = new Intent(context, NotificationService.class);
context.startService(intent);
}
}
NotificationService.java
public class NotificationService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
showNotifications();
return Service.START_STICKY;
}
private void showNotifications(){
NotificationHelper notificationHelper = new NotificationHelper(getApplicationContext());
NotificationCompat.Builder nb;
nb = notificationHelper.getChannelNotification("Title", "Description");
notificationHelper.getManager().notify(123, nb.build());
}
#Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
I schedule the alert like so:
...
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hours);
calendar.set(Calendar.MINUTE, minutes);
AlarmManager alarm_manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlertReceiver.class);
PendingIntent pending_intent = PendingIntent.getBroadcast(this, 0, intent, 0);
alarm_manager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pending_intent);
```
The problem is that from Android 8 calling startService from the background is not allowed:
In this code:
public class AlertReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
intent = new Intent(context, NotificationService.class);
context.startService(intent);
}
}
You can change from:
context.startService(intent);
To:
ContextCompat.startForegroundService(getApplicationContext(), intent)
And put this in your Manifest:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
This will at least allow your service to run from the background in your current implementation.
You then have 5 seconds to call startForeground() in your Service and post a Notification that lets the user know that your service is running or it will be terminated.
Also for what you are trying to do, I think you will get a better result with:
setExactAndAllowWhileIdle
Or
setAlarmClock
setExact does not work as the name implies. All the AlarmManager documentation needs to be read carefully and I would suggest studying "doze" in detail before attempting any Service implementations that rely on timing.
This is a notification that I used in a project (sorry for Kotlin language):
private fun createNotification() {
val notification = getServiceNotification("")
notificationManager.notify(NOTIFICATION_ID, notification.build())
}
private fun getServiceNotification(contentText: String): Notification.Builder {
var notification = Notification.Builder(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
notification = Notification.Builder(this, NOTIFICATION_SERVICE_CHANNEL_ID)
}
val openAppPendingIntent = PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
)
return notification
.setContentTitle("Notification Title")
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_main)
.setContentIntent(openAppPendingIntent)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(
NOTIFICATION_SERVICE_CHANNEL_ID,
NOTIFICATION_SERVICE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
).apply {
lightColor = Color.GREEN
lockscreenVisibility = Notification.VISIBILITY_PRIVATE
}
notificationManager.createNotificationChannel(notificationChannel)
}
Related
How can my app play a short sound every hour, even when it is not running in the foreground?
Try-1: I tried a handler, but this does not help.
Try-2: as #Marcin, suggested, I tried the AlarmManager. No sound is played when the mobile is inactive. When I open the app, then it plays the sound.
Try-3: Add a notification channel with high priority on it. This plays the sound at least once, even when the mobile is standby. Swiping the notification away won't play the sound again. Setting the notification autoCancel to false won't help.
private void setAlarm() {
alarmManager = (AlarmManager) getSystemService( Context.ALARM_SERVICE);
Intent intent = new Intent( this, AlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast( this, 0, intent, FLAG_IMMUTABLE);
alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP,
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
60000L, pendingIntent);
createNotificationChannel();
}
private void createNotificationChannel() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "My ReminderChannel";
String description = "Channel for the Alarm manager";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel( "myandroid", name, importance);
channel.setDescription( description);
NotificationManager notificationManager = getSystemService( NotificationManager.class);
notificationManager.createNotificationChannel( channel);
}
}
The AlarmReceiver is:
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent( context, MainActivity.class);
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, i, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "myandroid")
.setSmallIcon(R.drawable.add_checklist_item)
.setContentTitle("Wakeup")
.setContentText("Descsription")
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent( pendingIntent);
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
notificationManagerCompat.notify(12345, builder.build());
playRingtone();
}
public void playRingtone() {
try {
MediaPlayer mp = MediaPlayer.create(MainActivity.mainActivity, R.raw.tock);
mp.start();
mp.setOnCompletionListener(MediaPlayer::release);
} catch (Exception e) {
e.printStackTrace();
}
}
}
In the AndroidManifest.xml is:
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<receiver android:name="com.example.myapp.AlarmReceiver"/>
</application>
Of course I don't want my silent app draining my battery.
Use workmanager.
here is the link.
I have functionality in my app where a user will select the time when he wants to get notifications from the application. In this case, I am taking the input from user in this format 12:00 AM. I wrote a method for creating notifications in my app which is working fine but I want to call that method every day at a user-selected time. Also, if app is completely destroyed not even running in the background will this method be called at the user-selected time daily?
String user_time=tinyDB.getString("app_check_time"); // This is user selected time e.g, 12:00 PM
Calendar calendar = Calendar.getInstance();
Date date1 = null;
try {
date1=new SimpleDateFormat("h:m a").parse(user_time);
} catch (ParseException e) {
e.printStackTrace();
}
calendar.set(Calendar.HOUR_OF_DAY, date1.getHours());
calendar.set(Calendar.MINUTE, date1.getMinutes());
Date time = calendar.getTime();
System.out.println("hourr "+time);
timer = new Timer();
timer.schedule(new createNotification(), time);
You can use WorkManager for this. And yes, it'll run even if your app is completely dead.
From Android Docs:
WorkManager is a library used to enqueue deferrable work that is guaranteed to execute sometime after its Constraints are met.
You can see the guide here https://developer.android.com/topic/libraries/architecture/workmanager
The WorkManager API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
Hope it helps!
Have you considered using something like Quartz Scheduler? This library has a mechanism for scheduling tasks to run at a set period of time every day using a cron like expression (take a look at CronScheduleBuilder).
Some example code (not tested):
public class GetDatabaseJob implements InterruptableJob
{
public void execute(JobExecutionContext arg0) throws JobExecutionException
{
getFromDatabase();
}
}
public class Example
{
public static void main(String[] args)
{
JobDetails job = JobBuilder.newJob(GetDatabaseJob.class);
// Schedule to run at 5 AM every day
ScheduleBuilder scheduleBuilder =
CronScheduleBuilder.cronSchedule("0 0 5 * * ?");
Trigger trigger = TriggerBuilder.newTrigger().
withSchedule(scheduleBuilder).build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
}
}
There's a bit more work upfront, and you may need to rewrite your job execution code, but it should give you more control over how you want you job to run. Also it would be easier to change the schedule should you need to
Creating a long running service for this task
Intent intent = new Intent(this, ReminderService.class);
startService(intent);
Creating a reminderservice which extends service
public class ReminderService extends Service {
#Override
public void onCreate() {
Calendar calendar = Calendar.getInstance();
calendar.set(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
6,
0,
0);
setAlarm(calendar.getTimeInMillis());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// If we get killed, after returning from here, restart
return START_STICKY;
}
public void setAlarm(long timeInMillis) {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, ReminderReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
0, intent, 0);
if (alarmManager != null) {
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, timeInMillis,
2 * 60 * 1000 , pendingIntent); //AlarmManager.INTERVAL_DAY //2 * 60 * 1000 (2 minutes)
}
}
}
creating ReminderReceiver which extends BroadcastReceiver
public class ReminderReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
//do task to show notification
ShowNotification showNotification = new ShowNotification(context);
showNotification.showNotification("Method called");
}
}
create ShowNotification class
public class ShowNotification {
private static final int NOTIFICATION = 0;
private static final String NOTIFICATION_CHANNEL_ID = "100";
private Context context;
private NotificationManager notificationManager;
private ConnectivityManager conManager;
public ShowNotification(Context context) {
this.context = context;
if (notificationManager == null) {
notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
}
if (conManager == null) {
conManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
}
}
/**
* Show a notification while this service is running.
*
* #param key
*/
void showNotification(String key) {
String NOTIFICATION_CHANNEL_NAME = "NOTIFICATION_CHANNEL_NAME";
// In this sample, we'll use the same text for the ticker and the expanded notification
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME, importance);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
notificationManager.createNotificationChannel(notificationChannel);
}
final Intent notificationIntent = new Intent(context, SplashActivity.class);
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
notificationIntent, 0);
// Set the info for the views that show in the notification panel.
Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setWhen(System.currentTimeMillis())
.setContentTitle("Method Called")
.setContentText(key)
.setContentIntent(contentIntent)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.build();
// Send the notification.
notificationManager.cancel(NOTIFICATION);
notificationManager.notify(NOTIFICATION, notification);
}
}
In manifest file
<service
android:name=".ReminderService"
android:enabled="true"
android:exported="false" />
<receiver android:name=".ReminderReceiver" />
I have written a code that is supposed to display notifications. I am choosing hour and minute from TimePicker component (notifications popping up every day at given time), then create an intent for Notification receiver. The database is updated with proper info and everything is being set with the AlarmManager. The request code ("code" variable) is unique for each notification. I am pasting code snippets below:
SettingActivity class:
findViewById(R.id.buttonNotification).setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
int hour, minute, id;
Calendar calendar = Calendar.getInstance();
PendingIntent pendingIntent;
calendar.set(Calendar.HOUR_OF_DAY,timePicker.getCurrentHour());
calendar.set(Calendar.MINUTE, timePicker.getCurrentMinute());
Intent intent = new Intent(getApplicationContext(),Notifcation_receiver.class);
hour = timePicker.getCurrentHour();
minute = timePicker.getCurrentMinute();
if(extra!=null){
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),extra.getInt("Code"),intent,PendingIntent.FLAG_UPDATE_CURRENT);
intent.putExtra("Code",extra.getInt("Code"));
db.updateNotification(hour,minute,extra.getInt("ID"));
}
else{
int code= Notification.getID();
intent.putExtra("Code",code);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),code,intent,PendingIntent.FLAG_UPDATE_CURRENT);
db.insertNotification(hour,minute);
}
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(),AlarmManager.INTERVAL_DAY,pendingIntent);
startActivity(new Intent(SettingActivity.this, NotificationActivity.class));
}
});
}
Notification receiver class:
public class Notifcation_receiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
int code = intent.getIntExtra("Code",0);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Intent repeating_intent = new Intent(context,MainActivity.class);
repeating_intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context,code,repeating_intent,PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.arrow_up_float)
.setContentTitle("title")
.setContentText("text")
.setAutoCancel(false);
notificationManager.notify(code,builder.build());
}
}
I can't seem to find the problem, causing the lack of planned notifications. Thank You for any help.
The problem was, as Mehmed mentioned, no channel set for the NotificationService.
Logcat log:
"E/NotificationService: No Channel found for pkg=com.example.kuba.quizapp, channelId=null"
It worked on API 22, but for 25 and higher the notification must be build with extra channel info.
I made a service for receiving notification, every time data is updated at the back end. Here's the code for the service:
public class FeedbackService extends IntentService {
public FeedbackService() {
super("FeedbackService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d("MyService", "About to execute feedback call");
feedbackCheckCall(this);
}
private void feedbackCheckCall(final Context context){
//Call for getting checking data from backend.
}
private void sendNotification(Context context) {
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher_icon)
.setContentTitle("Feedback Reply")
.setContentText("You've a reply waiting for your feedback!")
.setVibrate(new long[]{500,500,500});
Intent notificationIntent = new Intent(context, navHomeActivity.class );
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
// Add as notification
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(0, builder.build());
}
}
Here's the code for the reciever :
public class FeedbackRecieiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent dailyUpdater = new Intent(context, FeedbackService.class);
context.startService(dailyUpdater);
Log.d("AlarmReceiver", "Called context.startService from AlarmReceiver.onReceive");
}
}
Here's the code from where I call it:
Calendar updateTime = Calendar.getInstance();
updateTime.setTimeZone(TimeZone.getDefault());
updateTime.set(Calendar.HOUR_OF_DAY,0);
updateTime.set(Calendar.MINUTE, 0);
updateTime.set(Calendar.SECOND, 0);
long intervalTime = 2*60*60*1000; //in milliseconds format is : h*m*s*1000
Intent intent = new Intent(context, FeedbackRecieiver.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,updateTime.getTimeInMillis(),intervalTime,pendingIntent);
My manifest declarations are :
<service android:name=".navFragments.feedbackSuppport.FeedbackService"/>
<receiver android:name=".navFragments.feedbackSuppport.FeedbackRecieiver"/>
The app is crashing when I use a signed copy with this error on startup:
Sending non-protected broadcast com.motorola.motocare.INTENT_TRIGGER from system 6836:com.motorola.process.system/1000 pkg com.motorola.motgeofencesvc
java.lang.Throwable
at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:18179)
at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:18779)
at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:512)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2905)
at android.os.Binder.execTransact(Binder.java:565)
Can someone please help me out with this? I'm really stuck here. Thanks in advance.
So, there was no issue with broadcast receiver or the service. The issue was while using proguard. I was not using it correctly. I had disabled it in the debug variant and enabled it in the release variant. The app was crashing due to that.
I want to display notification every morning at 9 AM from my app.
So I am using Notification Manager, Alarm Manager, BroadcastReciever and Service to make that possible.
But I have a problem, because the notification shows randomly. When I first start the app and set the time, it works OK, but later the app fires and shows notification at random time.
How I can solve that?
Here is my code:
MainActivity
#Override
protected void onStart() {
super.onStart();
setAlarm();
}
public void setAlarm(){
Calendar calendar = Calendar.getInstance();
Calendar now = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 15);
calendar.set(Calendar.MINUTE, 43);
calendar.set(Calendar.SECOND, 0);
if(calendar.getTime().after(now.getTime())) {
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmIntent = new Intent(MainActivity.this, HoroscopeNotification.class);
pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); }
}
HoroscopNotification (BroadcastReciever)
public class HoroscopeNotification extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent arg1) {
showNotification(context);
}
private void showNotification(Context context) {
Intent service1 = new Intent(context, AlarmService.class);
context.startService(service1);
}
}
AlarmService
public class AlarmService extends Service {
private static final int NOTIFICATION_ID = 1;
private NotificationManager notificationManager;
private PendingIntent pendingIntent;
#Override
public IBinder onBind(Intent arg0)
{
return null;
}
#SuppressWarnings("static-access")
#Override
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
Context context = this.getApplicationContext();
notificationManager = (NotificationManager)context.getSystemService(context.NOTIFICATION_SERVICE);
Intent mIntent = new Intent(this, MainActivity.class);
pendingIntent = PendingIntent.getActivity(context, 0, mIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentTitle("Horoskop");
builder.setContentText("Pročitajte današnji horoskop");
builder.setSmallIcon(R.drawable.ic_bik);
builder.setAutoCancel(true);
builder.setContentIntent(pendingIntent);
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}
You'll notice in the Android SDK Reference material for the AlarmManager.setRepeating() states:
Note: as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.
You need to use AlarmManager.set() on pre-APIv19 and AlarmManager.setExact() on APIv19+. When your PendingIntent is fired and you receive your Broadcast in your BroadcastReceiver.onReceive() you can set another exact alarm for the next day.
Alarm Manager Example
I think you should follow above link. From my point of view, your design pattern (setting alarm in Activity class is not a good approach). Instead (like showed in the answer above) you should set your alarm from a service. Also the code for notification goes in BroadcastReceiver class, method OnReceive (In the example it is commented "Put here YOUR code").
Good luck