Bring a singleInstance Activity Back Using a Notification - android

I have a normal activity, let's call it A, and it's the first activity presented to the user when opening the app. Activity A has a button that launches an activity, let's call it B, with launchMode set to singleInstance in the manifest. Activity B does some processing.
Users can press the home button and open the app again, which will present them with the starting activity, aka activity A. If users click on the button again (in activity A), they will be presented with activity B. In the case, activity B will not be restarted, i.e., onCreate will not be called, since it is singleInstance, which is what I want.
I want to make it easier for the users to come back to activity B once it started if they pressed the home button. So, I created an ongoing notification that lets the users bring activity B back. The ongoing notification will be canceled once activity B is finished.
Now to the problem, clicking the ongoing notification recreates activity B again, i.e., the onCreate is being called again. I don't know why the behavior here is not the same as clicking on the button on activity A. Here is how I created the notification:
Intent notifyIntent = new Intent(mContext, CallActivity.class);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(
mContext, 0, notifyIntent, PendingIntent.FLAG_NO_CREATE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_call_white_18)
.setContentTitle(title)
.setContentText(text)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(notifyPendingIntent);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext);
notificationManager.notify(NOTIFICATION_ID, builder.build());
Can anyone tell me why this is not working and how can I get it to work the way I described?
EDIT
Here is a snippet of my manifest:
<application
android:name="com.mnm.caller.IMApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.mnm.caller.activities.SplashActivity"
android:configChanges="orientation|keyboardHidden"
android:noHistory="true"
android:screenOrientation="portrait"
android:theme="#style/AppTheme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.mnm.caller.activities.LoginActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="#style/AppTheme.NoTitleBar"
android:screenOrientation="portrait" />
<activity
android:name="com.mnm.caller.activities.HomeActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="#style/AppTheme.NoTitleBar"
android:screenOrientation="portrait" />
<activity
android:name="com.mnm.caller.activities.CallActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
android:theme="#style/AppTheme.NoTitleBar" />
<service
android:name="com.mnm.caller.services.IMFirebaseMessagingService"
android:stopWithTask="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>

In a comment you wrote:
I'm actually developing a calling app. Activity A is the home screen,
while B is the calling activity. I want to let the users navigate back
to the home screen during a call, so I intentionally and knowingly
chose to create two instances of the app in recent apps (you can see
the exact behavior in the default phone app on Android)
If this is the case, you probably do want to have 2 separate tasks that you can switch between. To do this, you need to use taskAffinity. Your CallActivity should have launch mode of singleTask or singleInstance and you should set android:taskAffinity="call" or something like that. In order not to confuse your users, you should also provide a different android:label and a different android:icon for CallActivity so that when this task appears in the list of recent tasks it looks different than the rest of your app. Make sure that when you launch CallActivity you also set FLAG_ACTIVITY_NEW_TASK. You should also set this flag when you build the Notification.

You need to set FLAG_ACTIVITY_SINGLE_TOP to the Intent instance (in your case notifyIntent) before creating the PendingIntent object.
In other words:
Intent notifyIntent = new Intent(mContext, CallActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(
mContext, 0, notifyIntent, PendingIntent.FLAG_NO_CREATE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_call_white_18)
.setContentTitle(title)
.setContentText(text)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(notifyPendingIntent);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext);
notificationManager.notify(NOTIFICATION_ID, builder.build());

Related

Different behavior of notification fullScreenPendingIntent in Android 10 and Android 13

I am working on an Android application in which I want to open fullScreenPendingIntent from the notification when app is in the background (user press HOME button). It is an application which will not be uploaded to Google Store and has granted permissions for android.permission.USE_FULL_SCREEN_INTENT, android.permission.SYSTEM_ALERT_WINDOW, android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, android.permission.FOREGROUND_SERVICE, android.permission.POST_NOTIFICATIONS for Android 13 and more.
Part of the code from manifest
# just Splash activity on start and for permissions
<activity
android:name=".ui.splash.SplashActivity"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
# Main Activity
<activity
android:name=".MainActivity"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:launchMode="singleInstance"
android:exported="false">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
# Activity for call when app in background
<activity
android:name=".ui.activity.call.CallFromBackground"
android:screenOrientation="portrait"
android:launchMode="singleTop"
android:noHistory="true"/>
When the call is received from the core service (.so lib) I trigger Broadcast message and receiver is defined in MainActivity. In the next steps notification is created with contentPendingIntent, fullScreenPendingIntent and an Action.
val notificationBuilder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("$callWay - $callState - ${sessionState.callName}")
.setAutoCancel(false)
.setOngoing(true)
.setSilent(callNotificationAdditionalData.silent)
.setContentIntent(contentIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_CALL)
if (callNotificationAdditionalData.fullScreenPendingIntent != null) {
notificationBuilder.setFullScreenIntent(callNotificationAdditionalData.fullScreenPendingIntent, true)
}
if (callNotificationAdditionalData.answerActionPendingIntent != null) {
notificationBuilder.addAction(NotificationCompat.Action(null, context.getString(R.string.msg_answer), callNotificationAdditionalData.answerActionPendingIntent))
}
if (callNotificationAdditionalData.includeDeclinePendingIntent) {
notificationBuilder.addAction(NotificationCompat.Action(null, context.getString(R.string.msg_decline), declineIntent(context, sessionId)))
}
val channel = NotificationChannel(
CALL_CHANNEL_ID, context.getString(R.string.msg_call_in_progress), NotificationManager.IMPORTANCE_HIGH
).apply {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
}
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.createNotificationChannel(channel)
notificationManagerCompat.notify(sessionId, notificationBuilder.build())
Such notification on Android 10 is displayed and fullScreenPendingIntent (android:name=".ui.activity.call.CallFromBackground") is shown.
In Android 13 only notification is displayed and fullScreenPendingIntent does not appear. If I click on notification, contentPendingIntent is displayed (this is ok).
I tried a lot of stuff and my workaroud is not nice. For now if running version is Android 13 I manually (before notification is created) start MainActivity (with flags Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT - have no idea if this is needed) and then CallFromBackground activity.
val mainActivityToFront = Intent(context, MainActivity::class.java)
mainActivityToFront.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
context.startActivity(mainActivityToFront)
context.startActivity(!! Intent which is also used in fullScreenPendingIntent !!)
Is there a solution for this problem or it is not possible to fix this?
Maybe my architecture is completely wrong and I have to start foreground service for each call (multiple parallel calls are possible). Can foreground notification open fullScreenPendingIntent? There are also limitations when the app can start the foreground service. I already have one and it used for GPS locations.

TaskStackBuilder, multiple distinct PendingIntents, Notifications

I have problem with combination of TaskStackBuilder and distinct PendingIntents for notifications. Let me explain what it is about.
I have an IntentService which creates notification when something comes up. Sometimes it creates several independent notification. Why I don't merge notifications like Google said? Because each notification should open the same Activity, BUT with different extras in passed Intent. So here what a do:
Create new Intent with extras:
Intent notificationIntent = new Intent(this, ItemActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
notificationIntent.putExtra(ItemActivity.URL_KEY, feed.getUrl());
notificationIntent.putExtra(ItemActivity.FEED_ID, feed.get_id());
notificationIntent.putExtra(ItemActivity.TITLE, feed.getTitle());
And now is the tricky part - I want to open ItemActivity with proper back stack, this mean when I press Back button or Up in AppBar, I want to come back to parent Activity. So here's what I do (according to Google doc: http://developer.android.com/training/notify-user/navigation.html):
In AndroidManifest.xml I have:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ItemActivity"
android:label="#string/item_activity"
android:launchMode="singleTask"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
Then creating back stack:
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(ItemActivity.class);
stackBuilder.addNextIntent(notificationIntent);
And PendingIntent:
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
Finnaly - notfication:
NotificationCompat.Builder builder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
(...)
.setContentIntent(notificationPendingIntent)
(...)
mNotifyManager.notify(feed.get_id(), builder.build());
In this scenario, app creates different notification with proper back stack, BUT with the same PendingIntent.
When I replace requestCode while getting PendingIntent with, for example, feed.get_id() (or another different number for each PendingIntent like System.currentTimeMillis()) then tapping on notification brings Activity with proper Intent (distinct PendingIntent for each notification), BUT without back stack - Back and Up buttons close the applications.
I've tried deleting android:launchMode="singleTask" from manifest, do not adding flags when I've creating new Intent, read half of Internet, hundred of StackOverflow posts and nothing.
I haven't managed working combination of this two scenario - that is: open Activity with proper Intent and back stack.
In advance please do not write something like "just override onBackPressed and start activity from there" - I want to know what I'm doing wrong here and how to achieve this with proper tools.
After 4 hours of work finally i found the solution. It is very simple just you need to provide a different request codes for pending intents.
PendingIntent pendingIntent = stackBuilder.getPendingIntent((int) gcmMessage.getId() /*Unique request code for each PendingIntent*/, PendingIntent.FLAG_UPDATE_CURRENT);
Documentation for getPendingIntent() method TaskStackBuilder.getPendingIntent(int, int)

Unable to resume state of activity on clicking notification

Context: There is a toggleButton which toggles between 'Record' and 'Stop'. When record is clicked, a notification is created to tell the user that there is a background service running, even if the user has hit the home button etc. and moved out of the app. On clicking the notification, the user should be able to come to the activity and 'Stop' the recording and save the file.
The problem: If i click record (and thus toggling the button), then hit the home button, and then click the notification, it comes to the activity, but the toggleButton is in 'Record' instead of 'Stop'. i.e. it has not retained the previous state. I presume this is because a new instance of the activity is created instead of reusing the existing one.
The code:
public void showRecordingNotification(){
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setContentTitle("Recording in progress!");
mBuilder.setContentText("Go to the app to stop.");
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent appPendingIntent=PendingIntent.getActivity(this,0,notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(appPendingIntent);
Notification notification= mBuilder.build();
notification.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
The android manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="space.wavicle.sensorvector2d" >
<uses-feature
android:name="android.hardware.sensor.accelerometer"
android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name">
<service android:name=".DataLogger"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ReplayActivity"
android:label="#string/title_activity_replay"
android:parentActivityName=".MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="space.wavicle.sensorvector2d.MainActivity" />
</activity>
</application>
</manifest>
What I've tried: Googling around and hunting around SO, i found many people having similar sounding problems, and all solutions seemed to revolve around flags of the intents, of which i tried all the combinations that made sense. I also have android:launchMode="singleTask" (also tried android:launchMode="singleInstance") in the manifest. None of them seems to do the job.
you should ask yourself a different question in my opinion.
why does it matter for that button, if the activity is a new instance or not?
how exactly do you know your recording state? if you are saving the state in the activity, that means if the activity is somehow lost(crash) or killed, you lose your state. that's bad.
you say you have a service running in the background, I would let the service hold my state.
EDIT :
as for the instance issue, first of all, here's a link to google documentation so you can read further regarding that topic:
http://developer.android.com/guide/components/tasks-and-back-stack.html#ManifestForTasks
in short, there are 4 launchModes states, standard, singleTop, singleTask, singleInstance.
you can read the differences in the docs.
The way I see it I would recommend you to go with the "singleTask" state.
basically, in singleTask, if you open that activity again thru a notification for example, it will resume the current activity that is already open, you can debug it and see that you won't enter the usual onCreate, you will go thru onResume (and OnNewIntent ofc..)

Android notification Reorder to front doesn't work. Copied from APP that it has worked in

I've copied a code from dummy project testing Services and Notifications in which reoder to font worked like a charm.
Here is the code for notification (pretty same as in tutorials)
NotificationCompat.Builder nBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setAutoCancel(true)
.setContentTitle("Request awaiting")
.setContentText("There is a service request awaiting for Your reaction");
Intent resultIntent = new Intent(this, MainActivity.class);
resultIntent.setAction(Intent.ACTION_MAIN);
resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resultIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);//stackBuilder.getPendingIntent(0, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
nBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = nBuilder.build();
mNotificationManager.notify(1, notification);
Manifest looks like :
<application android:allowBackup="true" android:icon="#drawable/ld"
android:label="#string/app_name" android:theme="#style/AppTheme">
<activity
android:name="com.iraasta.cloudcab.driver.MainActivity"
android:launchMode="singleTop"
android:label="#string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
It looks exactly same in my other app, in which it perfectly brings app to front like when selecting from the home screen.
But in this one it calls OnCreate every time and loses whole state.
To bring your application to the front (if it is in the background) in whatever state it is in, or to launch your app (if it isn't aleady running), you need to launch your "root activity" (the one with ACTION=MAIN and CATEGORY=LAUNCHER) and set the flag Intent.FLAG_ACTIVITY_NEW_TASK. This will launch the activity in a new task (if your app isn't already running), or bring the existing task to the foreground (if your app is already running).
Since you are doing this from a Notification, you should be setting Intent.FLAG_ACTIVITY_NEW_TASK in the Intent you pass to the Notification. However, Android is nice enough to set this flag for you if you forget to do it.
I'm not exactly sure why setting Intent.FLAG_ACTIVITY_REORDER_TO_FRONT caused your problem, but I can imagine that this somehow confuses Android.
Not using any flag fixed it.
To people looking at this post in future.
If Your app uses single activity and You want to bring it to front after clicking the Notification just leave flags as integer 0.
It fixed my problem.
Thanks for suggestions
Bounty goes to the person that will explain why is this flag ruining everything.
Setting android:launchMode="singleTask" in a manifest for an activity you wanna bring to the front works for me

Android LaunchMode Does Not Work

I started an temp activity from notifiation, just show some text messages.Whatever I set launchMode=singleInstance or noHistory=true, The temp activity showed last time will show again when enter from "Recent open". I want temp activity to be shown only if I clicked notification, don't show in the "Recent open". Thank you in advance.
<activity
android:name=".NotifiticationDialog"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="#android:style/Theme.Translucent.NoTitleBar" >
Notification notification = new Notification(R.drawable.icon, context.getString(R.string.app_name), System.currentTimeMillis());
Intent intent = new Intent(context, NotifiticationDialog.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent activity = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
notifycation.setLatestEventInfo(context, context.getString(R.string.app_name), message, activity);
notifycation.flags = Notification.FLAG_AUTO_CANCEL;
_nm.notify(NotifiticationDialog.ID, text);
EDIT:
#Lalit Poptani, I tried what you suggest, but it is not waht I need. After I click temp activity with android:excludeFromRecents="true", My app disappeared in "Recent".(user can't find it, my all activity is excluded)
EDIT:
Fact: I have 3 tmp activity showing some text just like toast did, 2 were opened from widget and they didn't mixed with app stack. 1 were opend from notification, it always show itself individually from "Recent".
<activity
android:name=".AppwidgetDialog1"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="#android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name=".AppwidgetDialog2"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="#android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name=".NotifiticationDialog"
android:excludeFromRecents="true"
android:theme="#android:style/Theme.Translucent.NoTitleBar" >
For removing your Activity from Recent Apps you can use android:excludeFromRecents, so try adding android:excludeFromRecents="true" to your Activity tag.

Categories

Resources