Collapsing Notification Panel in Android 12 - android

My app has a notification, that has an action button.
When clicking the button, I want the notification panel (or drawer, you name it) to collapse.
I've found a bunch of solutions, all suggesting the same (one of them for example).
This solution doesn't work anymore, at least not for me (on a Oneplus 8t Android 12 device). When that command is called, the app crashes.
What's the updated way to collapse the notification panel?

I came up with an idea after thinking about this question for months. All you need is an empty activity that doesn't show anything. I think this is a little bit hacky, but I don't have better ideas.
First, create a dummy empty activity that shows nothing, and put the code you would like to do inside that dummy activity.
DummyActivity.class
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Do your work here, put the work you would like to do after pressing the button, such as send a broadcast, so your work will still be executed
//No setContentView() is being called, so nothing will be displayed after opening this activity.
finish();
}
Remember to add android:excludeFromRecents="true" for the dummy activity in AndroidManifest.xml so the dummy activity doesn't show in recent apps.
Second, use PendingIntent.getActivity() instead, don't use PendingIntent.getBroadcast(), so you should have something like this :
RemoteViews remoteviews = new RemoteViews(context.getPackageName(), R.layout.notification_layout);
Intent notificationIntent = new Intent(context, DummyActivity.class);
PendingIntent pendingIntent;
pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
remoteviews.setOnClickPendingIntent(R.id.MyButton, pendingIntent);
With this kind of implementation, after clicking the button, the transparent activity will be started, showing nothing but causing the notification panel to be collapsed.

Related

Android: How to distinguish CLEAR all events from notification bar from user action

According to the specification, .setDeleteIntent(pendingIntent) is associated to both actions (CLEAR all events from notification bar and user action like swiping).
My requirements are that when the user touches the notification that appears on the notification bar, he must be forwarded to the NotificationsList.class. This is done with my pendingInent:
PendingIntent sendPendingIntent = PendingIntent.getActivity(context, reminderId, new Intent(context, NotificationsList.class), PendingIntent.FLAG_UPDATE_CURRENT);
However, on clicking the CLEAR button, the user must not be navigated to the application at all. With the .setDeleteIntent(pendingIndent) I cannot fulfill the 2nd requirement. The user is still navigated to NotificationsList.class.
Is there a way to programmatically distinguish the CLEAR all notifications events fired from the CLEAR button from user actions like touch or swipe on the specific notification on the notification bar?
What you're describing is very obtuse behavior. You need only set the pending intent to your notification and when it is clicked, the intent that is backing it will be executed.
If your code is navigating the user back to the app when the notification is cleared, then you already have a problem with your design. If the user clears your notification you should NOT be trying to navigate them back. Hence the setDeleteIntent() should NOT be associated with starting any activity.
Note that the intent that is backed when you click the notification (setContentIntent()) and clear (setDeleteIntent()) the notification are basically two PendingIntents, they should not be the same, which is what your problem is describing.
You cannot distinguish the two events. As the documentation says:
Notifications remain visible until one of the following happens:
The user dismisses the notification either individually or by using "Clear All" (if the notification can be cleared).
The user clicks the notification, and you called setAutoCancel() when you created the notification.
You call cancel() for a specific notification ID. This method also deletes ongoing notifications.
You call cancelAll(), which removes all of the notifications you previously issued.
So there are basically three different events in the view of a programmer:
You dismisses the notification
The user clicks on the notification
The user dismisses the notification (either by swiping or clearing it)
The first event is fired by yourself by calling cancelAll() or cancel().
You can handle the second like (which you wanna do I think):
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
//....
.setContentIntent(sendPendingIntent);
And you can handle the third event like (as you have described above):
builder.setDeleteIntent(pendingIndent)
I don't recommend to start an activity after the user dismisses your notification, because the user won't expect it and it will be a bad user experience.
I hope I could help.
According to the design guidelines, the user can expect to interact with your notification using higher-level gestures like click, swipe, and pinch zoom. Responding instantly to a lower level event like touch would short circuit these gestures, so your requirements would violate the design guidelines and you should not implement it.
If the requirements are changed so that the user is forwarded when they click on the notification, there is no need to distinguish between swiping and clearing, which is impossible in any case.
So your issue should be resolved by changing one word in the requirements: touch --> click.
I googled deleteIntent to find some info for a problem which led me here.
English is my second language. Sorry for some misuse of words in advance. I'm an android newbie, just downvote the answer if it sucks :)
For your last question, just as #x-code and #bendaf said, it's impossible to
distinguish swiping and clearing.
I am following the codelabs about notifications and encountered the same question(The description in your title). So I decided to offer more detail about how to use .setDeleteIntent in your case. Maybe you had done that.
In your case, the wrapped intent is for starting an activity, so do the pendingIntent.
PendingIntent sendPendingIntent = PendingIntent.getActivity(context, reminderId, new Intent(context, NotificationsList.class), PendingIntent.FLAG_UPDATE_CURRENT);
But for performing a broadcast, e.g. doing some stuff when the notification is cleared, use:
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(yourCustomActionString), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setDeleteIntent(pendingIntent); // the pendingIntent will be sent when the notification is cleared
Then we need a custom broadcast receiver receive that custom action contained in the Intent object, in your case, this action relates to the clearing:
// Inside onCreate, register the broadcast receiver;
registerReceiver(new MyReceiver(), new IntentFilter(yourCustomActionString));
.
.
// Create an inner class
public class MyReceiver extends BroadcastReceiver {
public NotificationReceiver() {}
#Override
public void onReceive(Context context, Intent intent) {
// code inside will be executed when pendingIntent is sent
Log("taG", "Notification is cleared"); // a message will be logged if the notification is cleared
// for more than one action, using switch...case to decide
}
}

Android notification starting new instance of activity no matter what the launchMode and/or flags are set to

I've been doing a lot of research on this and I've hit a wall. I've read what seems like all of the material on this topic (inluding this, this, and this as well as the docs) and nothing is helping me in what I want to do.
Basically, in the case that the app is open on the user's phone, I just want a notification to redirect the user to the already-existing Activity in my app. I use a pattern like this:
private PendingIntent buildPendingIntentForNotification(String type) {
Intent resultIntent = new Intent(this, MainActivity.class);
resultIntent.setAction(Intent.ACTION_VIEW);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);// have tried without this and with Intent.FLAG_ACTIVITY_CLEAR_TOP
resultIntent.putExtra(CommonUtils.NOTIFICATION_TYPE, type);
PendingIntent resultPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return resultPendingIntent;
}
I've also tried declaring all 4 types of launchModes in the Android Manifest within the activity tag. No matter what I do, clicking on the notification (even if the app is the active foreground app on the screen and MainActivity is the active activity) always seems to restart the activity from onCreate. From what I can see during debugging, onDestroy is not called at any time between getting the notification and clicking on it. However, after the notification is clicked, onCreate is called and THEN onDestroy is called even though my activity is not being destroyed, which is very strange. I'm hoping someone can help me make sense of this because all of the launchMode and Intent.setFlags suggestions are not working for me. Thanks a lot!
Just for info's sake, here's the code I used to fix my problem (with credit to David's solution):
private PendingIntent buildPendingIntentForNotification(String type) {
Intent resultIntent = new Intent(this, MainActivity.class);
//resultIntent.setAction(Intent.ACTION_VIEW);
resultIntent.setAction(Intent.ACTION_MAIN);
resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resultIntent.putExtra(CommonUtils.NOTIFICATION_TYPE, type);
PendingIntent resultPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return resultPendingIntent;
}
There are 2 ways of doing this:
Notification to restore a task rather than a specific activity?
Resume application and stack from notification
However, you may also be seeing this nasty Android bug.
You cannot control and cannot be sure about the state of your activities on the Android, meaning (in your context) you can only start an activity that has been paused not one that has been destroyed. The OS decides which activities will keep paused and which will destroy and you have no control over that. When your activity leaves the foreground successive callbacks are being invoked at undefined times and to the OS's
What you can simply do is to save your activity's instance state in the onPause() method (which we know for sure that will be called as soon as the activity leaves the foreground) and the on onCreate() you can restore the activity with the data as it previously was.
Note: If you have for example Activity A and activity B and you start activity B from within activity A, then on activity B's onCreate() method you can getIntent().getExtras()

Android home screen widget mysteriously fails to receive touch events

I've got a somewhat graphically complex Android homescreen (via AppWidgetProvider) widget that I simply can't get to respond to touch events. The general idea is that tapping the widget should make it change modes for the next 4-5 seconds to display different information, but for the life of me it's showing no sign of ever receiving the Touch event.
The steps I've taken are as follows:
I've implemented an intent-filter within the Manifest.xml like so...
<action android:name="foo.kung.fancywidget.TOUCHED" />;
... and it's inside the <receiver /> container for the widget, right next to the expected APPWIDGET_UPDATE entry that Android Studio helpfully adds.
I've ensured that the Layout being used has the clickable attribute set to true on every single element (just to be thorough) including the top-level RelativeLayout itself.
I've defined the static string for the thing at the top of the ExtraFancyWidget.class, like so...
public static final String TOUCHED = "foo.kung.fancywidget.TOUCHED";
...and according to what I've been reading it should come through as the broadcast via the onRecieve handler when done like this...
if (TOUCHED.equals(intent.getAction())) {
Log.i("onReceive", "Touch event received");
}
...but with a Log.d entry at the top of the onReceive handler I can tell I'm not getting any sort of signals through there at all aside from the heartbeat coming from the system service every ten seconds.
Lastly, I'm assigning the intent just like I've been reading about
private PendingIntent createOnClickIntent(Context context) {
Intent intent = new Intent(context, ExtraFancyWidget.class);
intent.setAction(TOUCHED); // WHY DOESN'T THIS WORK?!?!?
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
...and it's literally the last thing happening in onUpdate before the view is updated
views.setOnClickPendingIntent(R.id.TheWholeWidget, createOnClickIntent(context));
appWidgetManager.updateAppWidget(appWidgetId, views);
What could I possibly be overlooking or not seeing at this point? I don't know of anything else I'm supposed to be doing or changing to make this work... it just mysteriously ignores the user. ADB never shows me any of the log entries (although it does show a number of other, silly log messages so I know that's working) that would indicate the widget ever sees me tapping it.
The problem is in step 5, where you're creating the intent. Do NOT set a specific class to the intent, rather create it with only an action, like this:
Intent intent = new Intent();
intent.setAction(TOUCHED); // this should work now
or the shorthand
Intent intent = new Intent(TOUCHED);
If it still doesn't work, consider putting a requestCode other than 0 when building the PendingIntent. Depending on the Android version you're building from, there were some bugs when using just 0.
return PendingIntent.getBroadcast(context, 1000, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Lastly, if this broadcast will be used only within your app (which is highly probable), consider using a LocalBroadcastManager.

How to start an app the same way Android Launcher does (resume)

There are a lot of questions/answers about how to start an application from within your application in Android. But those solutions do not produce the same flow as if an icon was tapped in Android launcher.
For example, I do this (this is used with notifications):
intent = context.getPackageManager().getLaunchIntentForPackage("com.test.startup");
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pendingIntent = PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then when I tap on notification the app is started, however, it is started somewhat differently than when I tap the icon in the App drawer. Specifically: with this approach my main Activity is always created (i.e. onCreate() then onResume() is called). However, if application was already started and then put in background, then starting it from Launcher will only cause onResume() of currently shown activity to be called (not onCreate() on the main one). Is there a way to trigger the same resume flow programmatically from within my app?
To summarize the task: when user taps on notification I need my app to be either started (if it's not already), or brought to the foreground in its current state (if it's in background) and have some data passed to it. The app will then take care of handling/rendering that data.
Your app is behaving the way it supposed to. Even if you try the launch the app from App drawer it will call the same callback. You have to understand the lifecycle. As your activity is in the background onCreate will not get called. But for the handling the data from the notification intent you should utilize callback method OnNewIntent() in activity. You should override this method and extract the data the from the new intent and should update UI. After onNewIntent onresume will be called.
I hope this solves your problem.
Here is my onPause code which works the way you expected i.e when user clicks on the notification it doesnt call onCreate again:
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
intent = new Intent(getApplicationContext(), PlayerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pIntent = PendingIntent.getActivity(getBaseContext(), 0, intent,0);
NotificationCompat.Builder noti =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_media_play)
.setContentTitle("Nepali Music And more")
.setContentText("Playing");
noti.setContentIntent(pIntent);
noti.setAutoCancel(true);
noti.setOngoing(true);
Notification notification = noti.getNotification();
notificationManager.notify(1, notification);
Focus mainly on the intent flags
You want to use the intent flags Intent.FLAG_ACTIVITY_CLEAR_TOP to find your activity and clear the stack above it. You also need the Intent.FLAG_ACTIVITY_SINGLE_TOP flag to prevent your activity from being recreated (to resume).
The Intent.FLAG_ACTIVITY_SINGLE_TOP is necessary since by default, the launch mode is "standard" which lets you create multiple instances of your activity. If you were to set your launch mode to SingleTop, then this flag own't be necessary

Change notification intent in Android

I have a service that shows a notification that I wish that will be able to go to a specific activity of my app each time the user presses on it. Usually it would be the last one that the user has shown, but not always.
If the activity was started before, it should return to it, and if not, it should open it inside of the app's task, adding it to the activities tasks.
In addition, on some cases according to the service's logic, I wish to change the notification's intent so that it will target a different activity.
How do i do that? Is it possible without creating a new notification and dismissing the previous one? Is it also possible without creating a new task or an instance of an activity?
No it wouldn't be possible to change the Activity once you have sent the notification.
You can start an Activity on your task stack that is not a problem, check out the notification service in the tutorial here:
http://blog.blundell-apps.com/notification-for-a-user-chosen-time/
You have to set a pending intent on the notification:
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, SecondActivity.class), 0);
// Set the info for the view that shows in the notification panel.
notification.setLatestEventInfo(this, title, text, contentIntent);
You can see the pending intent takes a normal intent "new Intent(this, SecondActivity.class" so if you want specific behaviour (like bringing to the top instead of starting a new activity. Add the flags like you would normally to this intent. i.e. FLAG_ACTIVITY_REORDER_TO_FRONT (something like that)
Since platform version 11, you can build a notification using Notification.Builder. The v4 support library has an equivalent class NotificationCompat.Builder.
You can't change the Activity once you've sent the notification, but you can update the notification with a new Intent. When you create the PendingIntent, use the flag FLAG_CANCEL_CURRENT. When you send the new notification, use the ID of the existing notification when you call NotificationManager.notify().
Also, you should be careful how you start your app. The Status Bar Notifications guide tells you how to set up the back stack.

Categories

Resources