25

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 :(

Community
  • 1
  • 1
Nanne
  • 64,065
  • 16
  • 119
  • 163
  • Are you intending to disable Lock functionality on your tap to Notification ? – Murtaza Khursheed Hussain Nov 18 '15 at 06:49
  • nope, I'm just trying to perfrom a bit of code, an API call in this case – Nanne Nov 18 '15 at 07:45
  • Thats a security breach. If you gain access to the app while being locked its a breach. As far as the question is concerned you can disable the keyguard, but it is not recommended – Murtaza Khursheed Hussain Nov 18 '15 at 07:54
  • 1
    No it's not, it's a deliberate setting to perform an action. There are several non-secure actions you can do while locked, for instance setting your player to pause, taking a picture, and if you set my app you can turn on your lights (if you have set up the app to do that). Please don't call things randomly a 'security breach'. – Nanne Nov 24 '15 at 08:10

3 Answers3

16

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

  1. Use a service
  2. 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);
Florian Barth
  • 1,392
  • 12
  • 25
  • In this case the intent is registered as action in the notification, in order to use it as content intent, just use the NotificationBuilder operation, you used in your original code. – Florian Barth Nov 18 '15 at 08:02
  • But as an answer to the question: you are saying thay yes, a service would work where the activity did not? – Nanne Nov 18 '15 at 08:15
  • 1
    Yes, my guess is that the activity is "linked" to UI and interaction and for that you need to unlock your screen. Chomp the snippets above into your code and let's find out :) – Florian Barth Nov 18 '15 at 08:18
  • I will! It might take me a couple of hours as it's not really day-job-fähig to work on private projects ;) – Nanne Nov 18 '15 at 08:52
  • Very true, looking forward to the results :) – Florian Barth Nov 18 '15 at 09:36
  • Just tried it: if you use `notificationBuilder.addAction` it should work. (not sure if that also applies to contentIntent) – Florian Barth Nov 18 '15 at 10:41
  • The action isn't working for me on the lock screen. It does show if the device isn't locked, but on the lock screen only the content is shown, even with 'show all notification content' set. I'll try without an action, see what happens – Nanne Nov 18 '15 at 18:16
  • hmm, this doesn't seem to work at all I'm afraid: when just adding it as a contentIntent, it asks for the unlock as well. – Nanne Nov 18 '15 at 18:29
  • I fixed it :) I changed just a single line, the rest is your code; if you agree with the code you can maybe add the `RemoteViews` part to you answer? I'll accept yours then, as it would be the most complete! Thanks for the help – Nanne Nov 18 '15 at 18:45
  • That's great news! Glad I could help, I added your feedback into my answer as requested. Cheers! – Florian Barth Nov 19 '15 at 12:18
  • btw you can display the actions on your lockscreen by doing a "zoom out gesture on the notification". e.g. https://www.youtube.com/watch?v=QHMuoiTIlRM – Florian Barth Nov 19 '15 at 12:18
  • Using setOnClickPendingIntent works but the behavior of what happens after the click is a little different than with setContentIntent. For one, the notification tray isn't automatically dismissed after a click. Also, on the lock screen, notifications usually need to be tapped twice, but this makes it work with 1 tap. – Flyview Feb 12 '17 at 06:57
5

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

  1. Use a service
  2. 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);
Community
  • 1
  • 1
Nanne
  • 64,065
  • 16
  • 119
  • 163
1

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.

farid_z
  • 1,673
  • 21
  • 11