Can't cancel a notification after updating it - java

I'm developing a countdown app, and currently trying to show a notification when you exit the app while the countdown is running. Correspondingly, I want the notification to disappear when the user returns to the app.
So far I've managed to make it work for a simple notification with static text, do the following: in MainActivity.java, in onStop(), I create an intent and initiate the service with startService(intent). Symmetrically, in onStart() I run stopService(intent) so that when you return to the app the service gets canceled. This works like a charm, the notification appears and disappears when it must.
The next step has been trying to make the notification show a text that varies (it will say "X minutes remaining"). According to the info out there, to update an existing notification you have to create a new one, give it the same ID as the existing one, and call .notify of a NotificationManager. When I do this the notification indeed gets updated correctly (the text changes as expected), BUT: now, returning to the main activity does not cancel the notification. The icon stays up there and doesn't get interrupted.
I've been trying to solve this for hours and hours. I've also tried hacks like sending signals via shared preferences to tell the service to stop, but for some reason, it seems to completely ignore the command stopself() too.
Does anybody have a suggestion of what could be the cause? Any help would be greatly appreciated. Here is the relevant code:
MainActivity.java:
#Override
protected void onStart() {
super.onStart();
Intent serviceIntent = new Intent(getBaseContext(), CounterService.class);
stopService(serviceIntent);
}
protected void onStop() {
super.onStop();
Intent serviceIntent = new Intent(getBaseContext(), CounterService.class);
startService(serviceIntent);
}
CounterService.java:
public class CounterService extends Service {
Notification notification;
NotificationManager notificator;
Intent intentNoti;
CountDownTimer counter;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
intentNoti = new Intent(this, MainActivity.class);
final PendingIntent pending = PendingIntent.getActivity(this, 0, intentNoti, 0);
final Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.common_full_open_on_phone);
//Countdown
counter = new CountDownTimer (30000, 1000) {
public void onTick(long millisUntilFinished) {
String time = String.valueOf(System.currentTimeMillis());
notification = new Notification.Builder(CounterService.this)
.setContentTitle("Name")
.setContentText(time)
.setSmallIcon(R.drawable.icon_start)
.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
.setContentIntent(pending)
.setOngoing(true).build();
notificator = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificator.notify(1001, notification);
}
public void onFinish() {
}
}.start();
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
counter.cancel();
}
}

First create a Timer like this
private Timer timer;
private TimerTask timerTask;
public void startTimer() {
timer = new Timer();
timerTask = new TimerTask() {
public void run() {
// Add your code
}
};
timer.schedule(timerTask, 1000, 1000); //
}
Also you need to stop your timer.
So
public void stoptimertask() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
Call StartTimer and StopTimer in OnStartCommand() and onDestroy() respectively. Add these lines in onDestroy()
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, Restarter.class);
this.sendBroadcast(broadcastIntent);

it can be handled in multiple ways, you have not stopped your timer
Note:- posting code in Kotlin
1)
override fun onDestroy() {
counter.cancel()
}
in your activity
override fun onResume() {
super.onResume()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}

Related

I'm trying to run foreground service in android but whenever i run my app, app got freezed and not showing any notification

I have created a Notification channel In the onCreate method of MapsActivity and called the method startService to start the service.
App stay froze until service execution ends
In logcat Errors caused by is not mentioned.
I have also mentioned using FOREGROUND_SERVICE in ManifestFile
Main Class
public class MapsActivity extends AppCompatActivity {
public static final String CHANNEL_ID = "FenceCalculateChannel";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
createNotificationChannel();
Intent serviceIntent = new Intent(this,BackGroundDistanceCalculate.class);
startService(serviceIntent);
}
public void createNotificationChannel() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(CHANNEL_ID,
"Fence Service Channel",
NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
BackGroundDistanceCalculate Service Class
public class BackGroundDistanceCalculate extends Service {
final public static String TAG = "BackGroundDistance";
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MapsActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
int i;
for(i=0;i<1000;i++)
{
Log.d(TAG,"Value of i is : "+ i);
SystemClock.sleep(1000);
}
Notification notification =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Calculating Distance")
.setContentText("Distance Calculation is running")
.setSmallIcon(R.drawable.ic_favorite)
.setContentIntent(pendingIntent)
.build();
//startForeground(1, notification);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
This code should create a Notificationchannel on App start but not working.
What changes may I make to make this App run?
Service runs works on main thread (UI thread) according to official doc, so it blocked UI, that's why your app freezed.
You should use IntentService which already has a worker thread to run your work.
Or you can manually create an AsyncTask or a Thread inside your service to handle your work.
I can see two things that you can fix to make it work.
You are making the BackGroundDistanceCalculate go to sleep for 1000 seconds which makes the Main thread to hang on you as instances of Service class runs on the Main thread. You can either start up a new thread or extend from IntentService (might not work in Pie+ if not using WorkManager)
You can try starting the service using ContextCompat.startForegroundService() to start your service and from within your service you will have to post the notification before you start doing your stuff.

Why does foreground service stop working when device go into sleep mode

I want to create an app that is constantly checking for location change and put the current location in the firebase (e.g. an app for runners).
Unfortunately the foregroundservice is being stopped or paused every time the device go into sleep mode.
For starters I wanted to create a foreground service that is continuously writing information to the base (that would be a time stamp or a simple string) every second.
After some time it just stops writing to firebase without calling stopself().
The service is working fine on the emulator (even if put to sleep), but stops when tested on a real device – in my case Huawei, Android 8.1.0.
What should I do to force service to run in every state of the device?
My MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent intent = new Intent(this, MyService.class);
intent.putExtra("action", "start");
startForegroundService(intent);
}
else {
Intent intent = new Intent(this, MyService.class);
intent.putExtra("action", "start");
startService(intent);
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent intent = new Intent(this, MyService.class);
intent.putExtra("action", "stop");
startForegroundService(intent);
}
else {
Intent intent = new Intent(this, MyService.class);
intent.putExtra("action", "stop");
startService(intent);
}
}
}
MyService:
public class MyService extends Service {
int i =0;
private String CHANNEL_ID = "2345";
#Override
public void onCreate() {
super.onCreate();
}
public MyService() {
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(1000, createNotification());
String action = intent.getExtras().getString("action");
switch (action){
case "start":
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
myfunction();
handler.postDelayed(this, 1000);
}
};
break;
case "stop":
stopfunction();
break;
}
handler.postDelayed(runnable, 1000);
return START_NOT_STICKY;
}
private void stopfunction() {
stopSelf();
}
private void myfunction() {
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("locations");
myRef.child("location").setValue(i);
i++;
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
#RequiresApi(Build.VERSION_CODES.O)
private void createChannel(){
NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.infoTxt),
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(false);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
}
private Notification createNotification(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createChannel();
}
Intent notificationItent = new Intent(this, MainActivity.class);
notificationItent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(this, 0, notificationItent, 0);
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setColor(ContextCompat.getColor(this, android.R.color.background_dark))
.setContentIntent(intent)
.setSmallIcon(R.drawable.ic_launcher_background)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setContentTitle("GPS Location")
.build();
}
}
I've tried everything: service, foreground service, broadcast receiver, jobSheduler, WorkerManager – nothing helped. Then I found it’s a new HUAWEI feature called “power-intensive app monitor “. It kills every app that runs in the background for a long time unless user gives special permissions to it.
The path to do this:
Settings -> Security & privacy -> Location services -> recent location requests: YOUR APP NAME -> Battery -> uncheck Power-intensive prompt, App launch: Manage manually: check all three positions: Auto-launch, secondary launch, run in background.
I don’t know is there a way to do this programmatically. I think the best way is to create a sort of help activity and explain the user what to do if application won’t work.
Foreground services generally should be used for task which require user attention such as visual processes.
use Background service instead

Run API every second in Background after Android App is killed in Oreo version

I am trying to Build a Android Application which will run every second and when app is closed or killed then also it should run continuously in Background.
When API response condition is satisfied it should show a Local Notification..
I have used Service Class for background Task. It was working fine in all version Except the Oreo Version (8.1v)
I have check website and Example related to it, I have find out that we can't perform background task in Oreo Version after the app is closed or killed.
So I tried to use startForeground() then also it is not working,
After many tries, finally I am asking this question here.
So please help me to run a API in Background when App is closed.
MainActivty.class
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(this, new Intent(this,MyService.class));
} else {
startService(new Intent(this,MyService.class));
}
}
MyService.class
public class MyService extends Service {
public static final int notify = 3000; //interval between two services(Here Service run every 5 Minute)
private Handler mHandler = new Handler(); //run on another Thread to avoid crash
private Timer mTimer = null; //timer handling
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onCreate() {
super.onCreate();
if (mTimer != null) // Cancel if already existed
mTimer.cancel();
else
mTimer = new Timer(); //recreate new
mTimer.scheduleAtFixedRate(new TimeDisplay(), 0, notify); //Schedule task
}
//class TimeDisplay for handling task
class TimeDisplay extends TimerTask {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
new ApiCallAsyncTask().execute(URL);
}
});
}
}
}
Notification Method which is called in ApiCallAsyncTask class
Notification notif;
#TargetApi(Build.VERSION_CODES.O)
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void notification(String Name, String time,String mId,int id){
Intent intent = new Intent(MyService.this, MainActivity.class);
String CHANNEL_ID = String.valueOf(id);
PendingIntent pendingIntent = PendingIntent.getActivity(MyService.this, 100, intent, PendingIntent.FLAG_ONE_SHOT);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, Name, NotificationManager.IMPORTANCE_DEFAULT);
notif = new Notification.Builder(MyService.this)
.setContentIntent(pendingIntent)
.setContentTitle("Reminder")
.setContentText("hello")
.setSmallIcon(R.drawable.logo)
.setOnlyAlertOnce(true)
.setColor(ContextCompat.getColor(MyService.this, R.color.colorPrimaryDark))
.setChannelId(CHANNEL_ID)
.build();
notificationManager.createNotificationChannel(mChannel);
}else {
notif = new Notification.Builder(MyService.this)
.setContentIntent(pendingIntent)
.setContentTitle("Reminder")
.setContentText("hello")
.setSmallIcon(R.drawable.logo)
.setOnlyAlertOnce(true)
.setColor(ContextCompat.getColor(MyService.this, R.color.colorPrimaryDark))
.build();
}
notif.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(id, notif);
startForeground(1, notif);
}
Thank You..
You can use combination of JobIntentService + AlarmManager(for scheduling) or JobScheduler API.
But I strongly recommend replace your approach with Firebase Cloud Messaging. So you will place business logic on server side and notify clients in special cases.

Android - Call a function from outside an activity (through AlarmManager and Notification)

Ok, so I have a main activity called 'Main.java'. This main activity starts an AlarmManager which fires an intent leading to 'AlarmReceiver.java'.
This 'AlarmReceiver.java' then creates a notification which has two buttons on it. One of the buttons is a deletion button, and so when the user clicks on that button, another intent is fired, leading it to 'DelPair.java'.
In DelPair.java, I modify a table in a Database, but then I need the UI of Main.java to reflect this change. I have created two functions in Main.java called updateArrayFromDB() and updateUIFromArray() to do this for me:
updateArrayFromDB() will sync an ArrayList created in Main.java to a
certain table in the DB.
updateUIFromArray() will change the UI of
Main.java to represent the ArrayList that has just been changed.
The problem is that I cannot call these two functions from DelPair.java (they don't exist in that space). I have come across Serializables in trying to find an answer but I don't know enough to know if they apply here or exactly how to implement them across the AlarmManager and the NotificationManager.
How can I access these methods from DelPair.java?
In Main.java:
public void updateArrayFromDB(){
//... The code for this is long and irrelevant
}
public void updateUIFromArray(){
//... The code for this is long and irrelevant
}
private void SendNotification() {
Intent intent = new Intent(this, AlarmReceiver.class);
//...
PendingIntent sender = PendingIntent.getBroadcast(this, 2 , intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP, 5000, notif_freq, sender);
}
In AlarmReceiver.java:
Intent delPairI = new Intent(context, DelPair.class);
PendingIntent delPairPI = PendingIntent.getService(context, 0, delPairI, PendingIntent.FLAG_UPDATE_CURRENT);
Notification noti;
noti = new Notification.Builder(context)
//...
.addAction(R.drawable.ic_delete_icon, "Delete the thing", delPairPI)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, noti);
and then in DelPair.java:
public class DelPair extends IntentService {
//...
#Override
protected void onHandleIntent(final Intent intent) {
//...
Intent it = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
getApplicationContext().sendBroadcast(it);
handler.post(new Runnable() {
#Override
public void run() {
//... here is where I update the database, which works perfectly
//now need to update the UI and array in Main.java
updateArrayFromDB(); //these lines
updateUIFromArray(); //obviously don't work
}
});
}
}
Why not use broadcasts ? in onHandleIntent just send a broadcast
Intent i = new Intent();
i.setAction(CUSTOM_INTENT);
//put relevant data in intent
getApplicationContext().sendBroadcast(i);
The broadcast receiver:
public class IncomingReceiver extends BroadcastReceiver {
private MainActivity act;
public IncomingReceiver(MainActivity main){
this.act = act;
}
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(CUSTOM_INTENT)) {
System.out.println("GOT THE INTENT");
// call the method on act
}
}
}
In your activity onResume - register new IncomingReceiver, onPause unregister
private IncomingReceiver receiver;
public void onCreate(Bundle bOs){
//other codes
receiver = new IncomingReceiver(this);
}
#Override
protected void onResume() {
IntentFilter filter = new IntentFilter();
filter.addAction(CUSTOM_INTENT);
registerReceiver(receiver, filter);
super.onResume();
}
#Override
protected void onPause() {
unregisterReceiver(receiver);
super.onPause();
}
Since you need to have an updated UI based on database changes, you can call updateArrayFromDB() and updateUIFromArray() in the onResume() method of your activity so the UI gets updated each time the user enters the activity.

Creating a background service that launches an app at a specific time.

I just started on android development and I'm trying to write and a service that runs in the background and will launch an app at a specific time.
The program I wrote is based on a tutorial I came across, basically the app has 2 buttons, a start and stop. Once the user presses the start button, it'll start the background service and will check the time and if the time is right, it'll launch the app.
However I noticed that it doesn't always checks the time, it only does it when the user presses the button. How do I make it so that, it'll keep checking the time, once the user presses the button?.
Here is my code.
MyService.java
public class MyService extends Service{
private static final String TAG = "MyService";
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
//Toast.makeText(this, "Congrats! MyService Created", Toast.LENGTH_LONG).show();
Log.d(TAG, "onCreate");
}
#Override
public void onStart(Intent intent, int startId) {
//Toast.makeText(this, "My Service Started", Toast.LENGTH_LONG).show();
startApp("com.example.myApp");
Log.d(TAG, "onStart");
}
#Override
public void onDestroy() {
Toast.makeText(this, "MyService Stopped", Toast.LENGTH_LONG).show();
Log.d(TAG, "onDestroy");
}
public void startApp(String packageName){
Calendar c = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
String strDate = sdf.format(c.getTime());
if(strDate == "09:00" || strDate == "15:00" || strDate == "21:00"){
Toast.makeText(this,strDate,Toast.LENGTH_LONG).show();
Toast.makeText(this,"Starting the App",Toast.LENGTH_LONG).show();
Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
if(intent != null){
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}
}
MainActivity.Java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//start the service
public void onClickStartServie(View V)
{
//start the service from here
startService(new Intent(this, MyService.class));
}
//Stop the started service
public void onClickStopService(View V)
{
//Stop the running service from here
//Service will only stop if it is already running.
stopService(new Intent(this, MyService.class));
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
I think using alarm manager will be a better option. Set an alarm for that time and receive broadcast in your receiver at that time like below code:
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AppConstants.ALARM_ID_TESTING, new Intent(
AppConstants.FILTER_TESTING), PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millisAfterCurrent, pendingIntent);
Add receiver in manifest:
<receiver
android:name=“TestReceiver"
<intent-filter>
<action android:name="filter.test.time” />
</intent-filter>
And receiver class as:
class TestReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(AppConstants.FILTER_TESTING))
{
// code here
}
}
}
Why dont you try Time Changed Receiver instead of service, Since using a background service is bad programming approah.A service keeps on running in background and takes a lot of system memory and a burden battery. Moreover system can stop any of extra services running at anytime for adjustment of memory issues.
A time change receiver is the best approach and in that you can compare time and open any app accordingly.
i suggest you this two ways:
Use the "new" ScheduledThreadPoolExecutor as replacement for Timer
Example:
int threadCount = 1;
ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(threadCount);
long initialDelay = 5;
long period = 10;
ScheduledFuture<?> task = service.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
// DO IT EVERY 10 SECONDS
}
}, initialDelay, period, TimeUnit.SECONDS);
[...]
//if you want you can cancel the task later
task.cancel(true); // mayInterruptIfRunning = true
Since Activities, Threads and Processes can be terminated anytime by Android OS it is not guaranteed that the job is always executed.
Therefore, it is better to use AlarmManager
Official howto: Scheduling Repeating Alarms
Another nice example: Repeat Alarm Example In Android Using AlarmManager

Categories