TL;DR
How can I make a notification that does some work from the lock-screen without unlocking? After clicking an action, a button on the notification or just the complete notification, I want to do an API call (without typing my unlock code)
Details
Goal
Based on the answer on this question I tried to make a notification with an action that works on the lockscreen without unlocking the device. The action is something that doesn't need any further interface or interaction (think 'send an API request').
Status
The notification and click do work with an unlocked device. However, when locked I still need to enter the unlock code first, so either there is something new going on, or I just misunderstood the way it is supposed to work.
If I understand correctly I can set my visibility to 'public' to show the content (this works), and instead of defining an action (which does't seem to be public) I can handle clicks on the (now visible) layout. I tried this with the below code, but obviously it doesn't work.
I have tried both sending the intent to my app and to a service, as florian suggested below.
Code
This is code where I start the notification (this lives in an Activity, code was shortened for your convenience )
private void startNotification() {
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
.setSmallIcon(R.drawable.abc_ic_menu_share_mtrl_alpha)
.setContentTitle("title text")
.setContentText("content text");
Intent openIntent = new Intent(MyMainActivity.this, MyMainActivity.class);
openIntent.setAction("some_string");
PendingIntent pOpenIntent = PendingIntent.getActivity(this, 0, openIntent, 0);
builder.setContentIntent(pOpenIntent);
RemoteViews view = new RemoteViews(getPackageName(), R.layout.notification);
builder.setContent(view);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotificationManager.notify(id, builder.build());
}
As said, I also tried with the service as florian suggested, with this as a call:
Intent yepIntent = new Intent(this, MyIntentService.class);
yepIntent.setAction("test");
yepIntent.putExtra("foo", true);
yepIntent.putExtra("bar", "more info");
PendingIntent yepPendingIntent = PendingIntent.getService(this, notificationId, yepIntent, PendingIntent.FLAG_CANCEL_CURRENT);
//builder.addAction(R.drawable.abc_ic_menu_share_mtrl_alpha, "My Action", yepPendingIntent);
builder.setContentIntent(yepPendingIntent);
The action didn't show up on the lock-screen, so I changed it to the setContentIntent you see above. The result is the same though, no action for me :(
Try using an IntentService.
Replace your intent target with your intent service:
Intent yepIntent = new Intent(context, MyIntentService.class);
yepIntent.putExtra("foo", true);
yepIntent.putExtra("bar", "more info");
PendingIntent yepPendingIntent = PendingIntent.getService(context, notificationId, yepIntent, PendingIntent.FLAG_CANCEL_CURRENT);
notificationBuilder.addAction(R.drawable.icon_of_choice, "My Action", yepPendingIntent);
Register your service in the Manifest:
<service
android:name="app.great.mypackage.MyIntentService"
android:exported="false"/>
Your Service could look like this:
public class MyIntentSerice extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
Log.d("myapp", "I got this awesome intent and will now do stuff in the background!");
// .... do what you like
}
}
UPDATE with feedback from Nanne
The trick seems to be to
Use a service
Add the intent not as an action or a contentIntent, but with the RemoteViews method.
Combined it will be:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
.setSmallIcon(R.drawable.abc_ic_menu_share_mtrl_alpha)
.setContentTitle("My notification")
.setContentText("Hello World!");
int notificationId = 1;
Intent yepIntent = new Intent(this, MyIntentService.class);
yepIntent.setAction("test");
yepIntent.putExtra("foo", true);
yepIntent.putExtra("bar", "more info");
PendingIntent yepPendingIntent = PendingIntent.getService(this, notificationId, yepIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// doesn't show up on my lock-screen
//builder.addAction(R.drawable.abc_ic_menu_share_mtrl_alpha, "My Action", yepPendingIntent);
// asks for unlock code for some reason
//builder.setContentIntent(yepPendingIntent);
// Bingo
RemoteViews view = new RemoteViews(getPackageName(), R.layout.notification);
view.setOnClickPendingIntent(R.id.notification_closebtn_ib, yepPendingIntent);
builder.setContent(view);
Combining the answer from the question I linked (Notification action button not clickable in lock screen) and the one #florian_barth gave above, I got it working
The trick seems to be to
Use a service
Add the intent not as an action or a contentIntent, but with the RemoteViews method.
Combined it will be:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
.setSmallIcon(R.drawable.abc_ic_menu_share_mtrl_alpha)
.setContentTitle("My notification")
.setContentText("Hello World!");
int notificationId = 1;
Intent yepIntent = new Intent(this, MyIntentService.class);
yepIntent.setAction("test");
yepIntent.putExtra("foo", true);
yepIntent.putExtra("bar", "more info");
PendingIntent yepPendingIntent = PendingIntent.getService(this, notificationId, yepIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// doesn't show up on my lock-screen
//builder.addAction(R.drawable.abc_ic_menu_share_mtrl_alpha, "My Action", yepPendingIntent);
// asks for unlock code for some reason
//builder.setContentIntent(yepPendingIntent);
// Bingo
RemoteViews view = new RemoteViews(getPackageName(), R.layout.notification);
view.setOnClickPendingIntent(R.id.notification_closebtn_ib, yepPendingIntent);
builder.setContent(view);
It also works with Broadcast receiver and setAction
PendingIntent pendingIntent = PendingIntent.getBroadcast(..
builder.addAction(..
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
On the lock screen swipe down on the notification to expand it and tap the action area to invoke the broadcast receiver without unlocking the phone.
Related
Would you please help me with the issue below?
I created a simple app that shows a notification for a incoming SMS. On that notification, I added a button to delete the SMS thru the notification.
Since I have a Samsung Gear S2, that Delete button is displayed on the Smart Watch and I can delete the SMS using my Gear S2.
Main problem is that when I delete the SMS using the Gear S2, the screen is wakening up. When I test using Gmail, same scenario just delete the email and keep the screen off.
So, could you please, help me to undestand why the screen is turning on?
Here, is how I create the notification (after receiving a SMS).
// Intent used to delete the SMS
Intent deleteIntent = new Intent(context, MessagingService.class);
deleteIntent.putExtra("notiID", id);
deleteIntent.putExtra("address", address);
deleteIntent.putExtra("date", date);
deleteIntent.putExtra("body", body);
PendingIntent deletePendingIntent = PendingIntent.getService(
context,
id,
deleteIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Intent used to start the app
Intent clickIntent = new Intent(context, MainActivity.class);
PendingIntent clickPendingIntent = PendingIntent.getActivity(
context,
id + 1,
clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Notification
NotificationCompat.Builder notiBuilder = new NotificationCompat.Builder(context);
notiBuilder.setSmallIcon(R.drawable.ic_message_white_32dp)
.setContentTitle(address)
.setContentText(body)
.setContentIntent(clickPendingIntent)
.addAction(R.drawable.ic_delete_white_32dp, context.getString(R.string.delete), deletePendingIntent)
.setLights(Color.BLUE, 3000, 3000);
Notification mNotificationBar = notiBuilder.build();
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
mNotificationManager.notify(id, mNotificationBar);
What I tested:
For now, I move the "SMS Deletion" code to a Service. That's why I'm using:
Intent deleteIntent = new Intent(context, MessagingService.class);
PendingIntent deletePendingIntent = PendingIntent.getService(....);
But I also tried to delete the SMS using the BroadcastReceiver (same result):
Intent deleteIntent = new Intent(context, SmsReceiver.class);
deleteIntent.setAction("com.test.simplesms.DELETE_MESSAGE");
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(....);
So, I'm not sure why the action configured by deletePendingIntent is turning the screen on.
Eventually, I could fix the error and I'm sharing here for future reference.
After debugging and researching, I discovered that I should extend my notification for wearable devices via WearableExtender.
This way, addAction() add the actions to Notification Bar while extend() add an WearableExtender which configure the actions that can be performed by the smartwatch (and this way, you can configure different stuff for Notification Bar and the Smartwatch)
// Intent used to delete the SMS
Intent deleteIntent = new Intent(context, SmsReceiver.class);
deleteIntent.putExtra("notiID", id);
deleteIntent.putExtra("address", address);
deleteIntent.putExtra("date", date);
deleteIntent.putExtra("body", body);
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(
context,
id,
deleteIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Intent used to start the app
Intent clickIntent = new Intent(context, MainActivity.class);
PendingIntent clickPendingIntent = PendingIntent.getActivity(
context,
id + 1,
clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// ADD THIS
// Add a wearable extender.. an wearable action
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
wearableExtender.addAction(new NotificationCompat.Action(R.drawable.ic_delete_white_32dp, context.getString(R.string.delete), deletePendingIntent));
// Notification
NotificationCompat.Builder notiBuilder = new NotificationCompat.Builder(context);
notiBuilder.setSmallIcon(R.drawable.ic_message_white_32dp)
.setContentTitle(address)
.setContentText(body)
.setContentIntent(clickPendingIntent)
.extend(wearableExtender) // ----> ADD THE WEARABLE HERE
.addAction(R.drawable.ic_delete_white_32dp, context.getString(R.string.delete), deletePendingIntent)
.setLights(Color.BLUE, 3000, 3000);
Notification mNotificationBar = notiBuilder.build();
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
mNotificationManager.notify(id, mNotificationBar);
I think your SmsReceiver class extends from WakefulBroadcastReceiver
I am starting stickynotification from my service using startForeground. Notification does show up however my settings, like title or intent to show up when clicked does not take effect.
#Override
public void onCreate()
{
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
Notification notification = new NotificationCompat.Builder(this)
//.setSmallIcon(R.mipmap.app_icon)
.setContentTitle("My Awesome App")
.setContentText("Doing some work...")
.setContentInfo("Server is running")
.setContentIntent(pendingIntent).build();
startForeground(1337, notification);
super.onCreate();
}
Notification shows app name and touch for more info or to stop....
When clicked it takes me to app info where i can forestop or uninstall it.
If anybody out there in this universe having same problem i suggest do not overclock your brain else you will endup missing small things like requesting permissions.
Setting up icon fixed my issue.
My Android notification Action buttons are not working at all. I have the following code in my service, and the receiver is NOT registered in the manifest because it makes no change. I can send the broadcast from another activity, and it works great, but there is a problem somewhere.
Here are the PendingIntents that pair with the buttons
Intent next = new Intent(getString(R.string.receiver_notification_media_change));
next.setAction(NOTIFICATION_MEDIA_CHANGE_NEXT);
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(getApplicationContext(), 0, next, PendingIntent.FLAG_UPDATE_CURRENT);
Intent last = new Intent(getString(R.string.receiver_notification_media_change));
last.setAction(NOTIFICATION_MEDIA_CHANGE_BACK);
PendingIntent pendingIntentLast = PendingIntent.getBroadcast(getApplicationContext(), 0, last, PendingIntent.FLAG_UPDATE_CURRENT);
Notification:
Notification.Builder mBuilder = new Notification.Builder(getApplicationContext())
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setSmallIcon(smallDrawableResId)
.addAction(R.drawable.icon1, "as", pendingIntentLast)
.addAction(R.drawable.icon2, "asdf", pendingIntentNext)
.setContentTitle(title)
.setContentText("title")
.setLargeIcon(icon)
.setContentIntent(pendingIntent) //to an activity. Works great
.setOngoing(true)
.setStyle(new Notification.MediaStyle()
.setShowActionsInCompactView(0, 1));
Here is the BroadcastReceiver which is declared in the class below.
private BroadcastReceiver notificationMediaChanger = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
System.out.println("RECEIVEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");
if(action.equals(NOTIFICATION_MEDIA_CHANGE_NEXT))
playNextSong();
else if(action.equals(NOTIFICATION_MEDIA_CHANGE_BACK))
playPreviousSong();
}
};
OnCreate the receiver is registered
registerReceiver(notificationMediaChanger, new IntentFilter(getString(R.string.receiver_notification_media_change))); //LocalBroadcastManager.getInstance(getApplicationContext()) appears to be equivalent.
And OnStop it is removed:
unregisterReceiver(notificationMediaChanger);
Your action strings do not match.
Intent next = new Intent(getString(R.string.receiver_notification_media_change));
next.setAction(NOTIFICATION_MEDIA_CHANGE_NEXT);
For some reason, you are replacing one action string with another. I do not know why.
registerReceiver(notificationMediaChanger, new IntentFilter(getString(R.string.receiver_notification_media_change)));
Here, you are using the first action string. Your Intent has the second action string. These are presumably not the same.
Also:
LocalBroadcastManager is not used by PendingIntent
Unless the Notification is only on the screen while your activity is on the screen (which would be bizarre), you need to register your receiver in the manifest
I want to add a tap action (exit (finish)) to a notification.
I'm making a simple app with several classes, and I want them all finished when I tap the notification.
Here is my notification code:
mMN = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification n = new Notification();
n.icon = R.drawable.ic_launcher;
n.tickerText = "Tap this notification to exit";
n.when = System.currentTimeMillis();
Intent nid = new Intent(MainActivity.this, stopservice.class);
PendingIntent ci = PendingIntent.getActivity(this, 0, nid,PendingIntent.FLAG_UPDATE_CURRENT);
CharSequence ct = "TAP";
CharSequence tt = "Tap here to exit";
n.setLatestEventInfo(this,ct,tt,ci);
mMN.notify(NOTIFICATION_ID, n);
I'm making a reference to stopservice class (where it is my stop service code) on Intent nid, but I'm not quite sure if it's a correct reference.
hope that my question is clear.
You should be using a notification builder:
mMN = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent nid = new Intent(MainActivity.this, stopservice.class);
// If you were starting a service, you wouldn't using getActivity() here
PendingIntent ci = PendingIntent.getActivity(MainActivity.this, NOTIFICATION_ID, nid, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(MainActivity.this);
builder.setContentTitle("TAP")
.setContentText("Tap here to exit")
.setTicker("Tap this notification to exit")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(ci)
.setAutoCancel(true); // auto cancel means the notification will remove itself when pressed
mMN.notify(NOTIFICATION_ID, builder.getNotification());
Your code is set to launch the Activity "stopservice" (which technically should be "StopService" with naming conventions), are you sure that class is an Activity?
Also, make sure your Activity is registered in your app's manifest:
<activity android:name="[package-name].stopservice" android:label="#string/app_name"/>
Since API level 16 (Jelly Bean), there is the possibility to add actions to a notification with
builder.addAction(iconId, title, intent);
But when I add an action to a notification and the action is pressed, the notification is not going to be dismissed.
When the notification itself is being clicked, it can be dismissed with
notification.flags = Notification.FLAG_AUTO_CANCEL;
or
builder.setAutoCancel(true);
But obviously, this has nothing to with the actions associated to the notification.
Any hints? Or is this not part of the API yet? I did not find anything.
When you called notify on the notification manager you gave it an id - that is the unique id you can use to access it later (this is from the notification manager:
notify(int id, Notification notification)
To cancel, you would call:
cancel(int id)
with the same id. So, basically, you need to keep track of the id or possibly put the id into a Bundle you add to the Intent inside the PendingIntent?
Found this to be an issue when using Lollipop's Heads Up Display notification. See design guidelines. Here's the complete(ish) code to implement.
Until now, having a 'Dismiss' button was less important, but now it's more in your face.
Building the Notification
int notificationId = new Random().nextInt(); // just use a counter in some util class...
PendingIntent dismissIntent = NotificationActivity.getDismissIntent(notificationId, context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setPriority(NotificationCompat.PRIORITY_MAX) //HIGH, MAX, FULL_SCREEN and setDefaults(Notification.DEFAULT_ALL) will make it a Heads Up Display Style
.setDefaults(Notification.DEFAULT_ALL) // also requires VIBRATE permission
.setSmallIcon(R.drawable.ic_action_refresh) // Required!
.setContentTitle("Message from test")
.setContentText("message")
.setAutoCancel(true)
.addAction(R.drawable.ic_action_cancel, "Dismiss", dismissIntent)
.addAction(R.drawable.ic_action_boom, "Action!", someOtherPendingIntent);
// Gets an instance of the NotificationManager service
NotificationManager notifyMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Builds the notification and issues it.
notifyMgr.notify(notificationId, builder.build());
NotificationActivity
public class NotificationActivity extends Activity {
public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(getIntent().getIntExtra(NOTIFICATION_ID, -1));
finish(); // since finish() is called in onCreate(), onDestroy() will be called immediately
}
public static PendingIntent getDismissIntent(int notificationId, Context context) {
Intent intent = new Intent(context, NotificationActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent dismissIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return dismissIntent;
}
}
AndroidManifest.xml (attributes required to prevent SystemUI from focusing to a back stack)
<activity
android:name=".NotificationActivity"
android:taskAffinity=""
android:excludeFromRecents="true">
</activity>
I found that when you use the action buttons in expanded notifications, you have to write extra code and you are more constrained.
You have to manually cancel your notification when the user clicks an action button. The notification is only cancelled automatically for the default action.
Also if you start a broadcast receiver from the button, the notification drawer doesn't close.
I ended up creating a new NotificationActivity to address these issues. This intermediary activity without any UI cancels the notification and then starts the activity I really wanted to start from the notification.
I've posted sample code in a related post Clicking Android Notification Actions does not close Notification drawer.
In new APIs don't forget about TAG:
notify(String tag, int id, Notification notification)
and correspondingly
cancel(String tag, int id)
instead of:
cancel(int id)
https://developer.android.com/reference/android/app/NotificationManager
In my opinion using a BroadcastReceiver is a cleaner way to cancel a Notification:
In AndroidManifest.xml:
<receiver
android:name=.NotificationCancelReceiver" >
<intent-filter android:priority="999" >
<action android:name="com.example.cancel" />
</intent-filter>
</receiver>
In java File:
Intent cancel = new Intent("com.example.cancel");
PendingIntent cancelP = PendingIntent.getBroadcast(context, 0, cancel, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Action actions[] = new NotificationCompat.Action[1];
NotificationCancelReceiver
public class NotificationCancelReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Cancel your ongoing Notification
};
}
You will need to run the following code after your intent is fired to remove the notification.
NotificationManagerCompat.from(this).cancel(null, notificationId);
NB: notificationId is the same id passed to run your notification
You can always cancel() the Notification from whatever is being invoked by the action (e.g., in onCreate() of the activity tied to the PendingIntent you supply to addAction()).
Just put this line :
builder.setAutoCancel(true);
And the full code is :
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(android.R.drawable.ic_dialog_alert);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.co.in/"));
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
builder.setContentIntent(pendingIntent);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.misti_ic));
builder.setContentTitle("Notifications Title");
builder.setContentText("Your notification content here.");
builder.setSubText("Tap to view the website.");
Toast.makeText(getApplicationContext(), "The notification has been created!!", Toast.LENGTH_LONG).show();
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
builder.setAutoCancel(true);
// Will display the notification in the notification bar
notificationManager.notify(1, builder.build());
Just for conclusion:
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
Intent intent = new Intent(context, MyNotificationReceiver.class);
intent.putExtra("Notification_ID", 2022);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
...);
Notification notification = new NotificationCompat.Builder(...)
...
.addAction(0, "Button", pendingIntent)
.build();
notificationManager.notify(2022, notification);
and for dismiss the notification, you have two options:
approach 1: (in MyNotificationReceiver)
NotificationManager manager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
manager.cancel(intent.getIntExtra("Notification_ID", -1));
approach 2: (in MyNotificationReceiver)
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
manager.cancel(intent.getIntExtra("Notification_ID", -1));
and finally in manifest:
<receiver android:name=".MyNotificationReceiver" />
builder.setAutoCancel(true);
Tested on Android 9 also.