Implementing exit app button in the persistent notification - java

My app runs a background service indicated by the persistent notification. Users need to use the toggle button in MainActivity to turn on/off this service and thus to remove the persistent notification.
Now I want to implement a notification action that can turn this service off & as well as the toggle within MainActivity. All in all, it should be an exit button to close my app and background service directly from the notification.
How do I achieve this?
note that I have two different java files, one for NotificationService
and the other is MainActivity. The toggle belongs to MainActivity.
edit: Is this okay to call System.exit(0) if I use pending intent with
BroadCastReceiver to exit from the app completely?

You have to use PendingIntent with BroadcastReceiver or Service to perform this. Here is an example of PendingIntent with BroadcastReciever.
Build a Notification
public static void createNotif(Context context){
Intent intentAction = new Intent(context,StopServerBroadcast.class);
//This is optional if you have more than one buttons and want to differentiate between two
intentAction.putExtra("action","actionName");
pIntent = PendingIntent.getBroadcast(context,1,intentAction,PendingIntent.FLAG_UPDATE_CURRENT);
drivingNotifBldr = (NotificationCompat.Builder) new NotificationCompat.Builder(context, CHANNEL_NAME)
.setSmallIcon(R.drawable.steeringwheel)
.setContentTitle("Stop Service")
.setContentText("Example Text")
.addAction(R.drawable.smallmanwalking, "On/off", pIntent)
.setOngoing(true);
...
}
Now the receiver which will receive this Intent
public class StopServerBroadcast extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Toast.makeText(context,"recieved",Toast.LENGTH_SHORT).show();
String action=intent.getStringExtra("action");
if(action.equals("action1")){
performAction1();
/*
Code that will stop your service
*/
}
}
}
Register Receiver in Manifest
<receiver
android:name=".StopServerBroadcast"
android:enabled="true" />

Related

Open Activity on notification button click when app is closed

I'm trying to open the MainActivity when the user clicks a button in my notification, while the app is only running in the background with a service. When the button is clicked, these lines are triggered in the Service class:
Intent openApp = new Intent(this, MainActivity.class);
openApp.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(openApp);
I've checked it, and the lines are triggered, so there's no problem in reacting to the button's click, the Activity won't open though.
Any suggestions? Why isn't this working for me and how can I make it work?
Edit
I was asked for some more code, so in my onStartCommand() inside my Service, if it starts with a stop-action within its intent, I call the killService() method, which kills the Service, starts the MainActivity and do some other stuff:
if (action != null && action.equals(ACTION_STOP_SERVICE)) {
killService();
}
To set the Notifications button, I use this code:
Intent stopActionIntent = new Intent(this, TimerService.class);
stopActionIntent.setAction(ACTION_STOP_SERVICE);
PendingIntent stopActionPendingIntent = PendingIntent.getService(this, 1, stopActionIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.addAction(R.drawable.stop, "Stop", stopActionPendingIntent);
And as I said, the button already reacts to the user clicking on it, so that's not the problem.
You can try to receive the click in a BroadcastReceiver and then open activity from there.
Try this to add a action button o your notification:
timerNotificationBuilder.addAction(createNotificationActionButton("STOP");
Where the createNotificationActionButton method is this:
public NotificationCompat.Action createNotificationActionButton(String text){
Intent intent = new Intent(this, StopwatchNotificationActionReceiver.class);
#SuppressLint("InlinedApi") PendingIntent pendingIntent = PendingIntent.getBroadcast(this, new Random().nextInt(100), intent, PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Action(0, text, pendingIntent);
}
Create a class named StopwatchNotificationActionReceiver and make it extent a BroadcastReceiver`. This is the code for that class:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class StopwatchNotificationActionReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
PrefUtil.setIsRunningInBackground(context, false);
PrefUtil.setTimerSecondsPassed(context, 0);
PrefUtil.setWasTimerRunning(context, false);
context.stopService(MainActivity.serviceIntent);
Intent activityIntent = new Intent(context, MainActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActvity(activityIntent);
}
}
Also you need to register that receiver in your manifest like this:
<receiver android:name="StopwatchNotificationActionReceiver"/>
Where the MainActivity.serviceIntent is a public static variable which looks like this:
public static Intent serviceIntent;
And this intent is only used to start the service like this:
//In onCreate
serviceIntent = new Intent(this, TimerService.class);
//In onPause
PrefUtil.setTimerSecondsPassed(this,seconds);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
}
Or you can try the simple method:
if (action != null && action.equals(ACTION_STOP_SERVICE)) {
Context context = this;
Intent activityIntent = new Intent(context, MainActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActvity(activityIntent);
killService();
}
Edit
Another solution is here. Again. You need to refer to my repo as I have made changes to the files in order to complete your task. In the service class, refer to this method. There, I start the activity if the action is reset(r). Or else, it opens the broadcast receiver. Then, in the activity, I receive that extra in the onResume() method. If the reset button is not clicked, it opens the Receiver class.
And as always, you can view the result of the app from here.
I hope that code will do your work.
I found it! See this answer.
This answer suggests enabling ScreeanOverlay settings because as of Android 10 and later you can no longer open an activity from the background just by calling the lines I've used.
To make it work, you'd have to add this permission through your Manifest.xml:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
And then the user would have to enable the Display over other apps setting.
I searched for an option to get the user to this setting more easily and found this answer.
This answer gives a code that redirects the user to the Display over other apps setting
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 0);
}
and then I guide the user with the notification's content (text) on how to enable the setting.
Once the setting is enabled, The lines I've used before work.\
So, problem solved?
Not Completely Solved
this whole configuration described above works, but only if the app is not killed.
If the app was killed and I try the method listed above, the app joins the recent apps list, but won't open and show up.
A solution that solves this issue as well will be accepted as an answer instead of this one.

How do I detect if device is charging from background

I have searched thick and thin through the Developer notes for android and am unable to find out how to perform a specific action when a device is plugged in, and perform another action when unplugged.
I have attempted to set a Broadcast Receiver like below however it will not run:
<receiver android:name=".PowerConnectionReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
</receiver>
I understand that from API 26 and up, you can no longer receive some broadcasts registered in the manifest, and must register them dynamically.
I need to have this run in the background but can't figure out how? This article updated in 2019 (after api 26) implies that I should be able to do it.
The charging status can change as easily as a device can be plugged in, so it's important to monitor the charging state for changes and alter your refresh rate accordingly.
The BatteryManager broadcasts an action whenever the device is connected or disconnected from power. It's important to receive these events even while your app isn't running particularly as these events should impact how often you start your app in order to initiate a background update so you should register a BroadcastReceiver in your manifest to listen for both events by defining the ACTION_POWER_CONNECTED and ACTION_POWER_DISCONNECTED within an intent filter.
My end goal is to call the broadcast receiver whenever device is plugged in or unplugged.
How would I go about implementing this?
The best way to accomplish this is by having a service run in the background so that you can receive the broadcast from android without the user using the app.
Start by registering a service in your manifest file.
<Service android:name=".serviceName"/>
Now create your class with the same name as registered in the manifest above, this class must extend Service.
public class BackgroundService extends Service {
private static final int NOTIF_ID = 1;
private static final String NOTIF_CHANNEL_ID = "AppNameBackgroundService";
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
//Add broadcast receiver for plugging in and out here
ChargeDetection chargeDetector = new ChargeDetection(); //This is the name of the class at the bottom of this code.
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(Intent.ACTION_POWER_CONNECTED);
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
this.registerReceiver(chargeDetector, filter);
startForeground();
return super.onStartCommand(intent, flags, startId);
}
private void startForeground() {
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
startForeground(NOTIF_ID, new NotificationCompat.Builder(this,
NOTIF_CHANNEL_ID) // don't forget create a notification channel first
.setOngoing(true)
.setSmallIcon(R.mipmap.final_icon)
.setContentTitle(getString(R.string.app_name))
.setContentText("Background service is running")
.setContentIntent(pendingIntent)
.build());
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
NOTIF_CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
class ChargeDetection extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Now check if user is charging here. This will only run when device is either plugged in or unplugged.
}
}
}
You need to register this receiver from code
You could run a WorkManager to run once every 15 minutes (the minimum is once every 15 minutes)
register the receiver, check if it's charging
unregister the receiver

How to properly add new Activity onto top of the stack after tapping notification

I'm writing a really big app with plenty of activities. I have one class running in the background that is checking are there any changes on the server and perform appropriate action. One of the actions is to send notification to user about employee's clock in, out and similar. That kind of notification is clickable, and it should open employee's contact page PeopleSingleScene_Activity, and that is working as expected. However, when I click back button, application quits (no parent activities).
Code for creating notification with pending intent is as following:
public void sendEmployeeWorkNotification(String ticker, String title, String text, String loc, int employee_id) {
PendingIntent pendingIntent;
Intent intent;
Context currentContext = G.context;
if (loc.equals("LOC")) {
intent = new Intent(currentContext, PeopleSingleScene_Activity.class);
intent.putExtra("people_id", employee_id);
pendingIntent = PendingIntent.getActivity(currentContext, 0, intent, 0);
} else {
//some other actions
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(currentContext, CHANNEL_ID)
.setSmallIcon(R.drawable.vector_notif)
.setTicker(ticker)
.setContentTitle(title)
.setContentText(text)
.setColor(ContextCompat.getColor(currentContext, R.color.rcOutcome))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(currentContext);
notificationManager.notify(G.simpleMessageCounter, builder.build());
simpleSong(R.raw.notificationsimple);
G.simpleMessageCounter++;
}
G.context is the last activity context opened... It is a static variable in global class G. I'm creating it in a way:
public class SomeActivityClass extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_layout);
G.context = this;
....
}
....
}
I have also tried with context from my App class for retrieving string values or anything else with a context in classes without activities ...
public class App extends Application {
private static Context mContext;
#Override
public void onCreate() {
super.onCreate();
mContext = this;
}
public static Context getContext(){
return mContext;
}
public static String getStr(int resid) {
return mContext.getResources().getString(resid);
}
}
When I use this context from App.getContext(); result is the same, after tapping back button, application quits.
Question is: Is there a problem with a context for creating pending intent and notification, or there is an issue with intent creation flags? I have tried lot of combinations with contexts and flags, but none of them works. And I cannot write (or I don't know maybe) some listeners on each activity to listen for this kind of event and open new intent from itself. There will be over 50 activities...
You have to make launch mode for this activity single top <activity android:launchMode="singleTop" />
Read more about Android Activity Launch Mode
Finally found a solution, thanks to my buddy Miljan.
When creating pendingIntent, just needed a proper flag. So instead of
pendingIntent = PendingIntent.getActivity(currentContext, 0, intent, 0);
just put a flag
pendingIntent = PendingIntent.getActivity(currentContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
and it works regularly. I can go back to previous activity after displaying activity intitiated by tapping notification.

Update textview of MainActivity from class that extends Service

I'm trying to create an android app that communicate with Bluetooth low energy module. I am able to connect to module, read data and so on. But the problem is i do not know how to update textview from class that extends Service (the one in which whole connection to BLE is going on). I know there is like BILLION or even more posts like these, but believe me i tried.
Here is MainActivity: http://pastebin.com/6yaP0dYM
and here is a class that extends Service: http://pastebin.com/cYuAUina
If someone could provide me some tips to solve my issue that would be more than great!
Create Broadcast Receiver and broadcast that in Service.
If your Activity is ope, register receiver and in onReceive() update the UI.
The receiver should be in the Activity...
Below is the working code
https://stackoverflow.com/a/14695943/1403112
You can get values through broadcast receiver......as follows, First create your own IntentFilter as,
Intent intentFilter=new IntentFilter();
intentFilter.addAction("YOUR_INTENT_FILTER");
Then create inner class BroadcastReceiver as,
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
/** Receives the broadcast that has been fired */
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction()=="YOUR_INTENT_FILTER"){
//HERE YOU WILL GET VALUES FROM BROADCAST THROUGH INTENT EDIT YOUR TEXTVIEW///////////
String receivedValue=intent.getStringExtra("KEY");
}
}
};
Now Register your Broadcast receiver in onResume() as,
registerReceiver(broadcastReceiver, intentFilter);
And finally Unregister BroadcastReceiver in onDestroy() as,
unregisterReceiver(broadcastReceiver);
Now the most important part...You need to fire the broadcast from wherever you need to send values..... so do as,
Intent i=new Intent();
i.setAction("YOUR_INTENT_FILTER");
i.putExtra("KEY", "YOUR_VALUE");
sendBroadcast(i);
....cheers :)

Broadcast Action: ACTION_CAMERA_BUTTTON

Is there any way after this action has been broadcast via a receiver to determine that the action has finished? i.e the user has exit camera mode and is doing something else?
You can register a broadcast receiver to receive this intent.
Note however that this intent will be broadcasted every time the user pushes the camera button, and is thus not directly related to any specific "action" (e.g. taking a photo).
BroadcastReceiver myReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do whatever you want to do
}
};
registerReceiver(myReceiver, new IntentFilter(Intent.ACTION_CAMERA_BUTTON));
unregisterReceiver(myReceiver);
Is there any way after this action has been broadcast via a receiver to determine that the action has finished?
No. For starters, there may be no "action" to "finish", and there is no definition of "action".

Categories