Resume background activity on notification - android

I've read some answers here, I think I have what I need in order to achieve my result, but I need some help.
My app launches an notification on specific conditions, and I need my app to behave as follow:
if there is an instance of my main activity running in background I need to make it to the foreground (I found this on the site: intent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);, so I think this point is solved;
if there isn't any activity of the app running in background I need to start the app from the beginning (and this can be achieved starting the launcher activity of the app.);
My question is: how can I make the app search for any istance of itself running in background? Because the activity that I need to reorder to front with the Intent flag is different from the launcher activity.
The notification is handled by a service that check periodically some infos from the internet.
Thanks for the help.

What you need is just a simple Activity that decides what to do. Here is an example:
public class NotificationActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if the app was already running
if (isTaskRoot()) {
// App wasn't running, so start the app from the beginning
Intent startIntent = new Intent(this, MyStartingActivity.class);
startActivity(startIntent);
} else {
// App was already running, bring MainActivity to the top
Intent reorderIntent = new Intent(this, MainActivity.class);
reorderIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(reorderIntent);
}
// We are done now so just finish
finish();
}
}
Set up your notification to start this activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).

Related

Detect if user starts an application, android

Thanks in advance for the help.
I have an app that can be started by either the user physically starting the app (like you would any normal app) or by a repeating service. Depending on what starts the app (the user or the service) I want to preform different initialization actions. How might I be able to detect if an user starts the app without doing anything custom (I imagine that there has to be some kind of built in setting in android for me to determine this)?
If service, that starts your Activity, is yours service, you can put some custom information (using Intent#putExtra for example) in Intent you use to start Activity from Service.
In Activity you can use Activity#getIntent(), that returns the intent that started this activity.
If you started Activity from Service, that Intent will be the one you passed in Service#startActivity, and will have your custom information. Otherwise, that was not your Service, that started your Activity.
That could look somehow like that, for example:
//in Activity
public static final String EXTRA_STARTED_FROM_MY_SERVICE = "com.example.extra_started_from_sevice";
private boolean wasActivityStartedFromService() {
Intent startingIntent = getIntent();
//assuming you will use Intent#putExtra in your service when starting activity
return startingIntent.getBooleanExtra(EXTRA_STARTED_FROM_MY_SERVICE, false);
}
//...
//in Service
//...
Intent startingIntent = new Intent(this, MainActivity.class);
startingIntent.putExtra(MainActivity.EXTRA_STARTED_FROM_MY_SERVICE, true);
startActivity(startingIntent);

Can I detect if Android has killed the application (task process) from a Notification Intent / PendingIntent?

The Android OS kills processes when it's low on memory. Scenario: Android kills the app process and I re-open it through either the Android launcher or the recent-task list (long press home button). I can check if Android killed my app process in the onCreate() method of the most recently viewed activity using:
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// Re-initialise things that killing the app would have destroyed
}
}
However, if Android kills the app process and I re-open it through a Notification using an Intent packaged inside a PendingIntent, I don't know how to determine if the app process was killed by Android. Subsequently I do not re-initialise things that killing the app process would have destroyed.
Is there a way to determine if Android killed the application process when opening a new Activity from a Notification?
I have found one hacky solution to this problem. Using: Android: always launch top activity when clicked on notification I can open the activity on top of the stack which is passed a savedInstanceState if Android killed the app process and deal with re-initialisation. Each activity is then responsible for redirecting the user to the appropriate activity using Extras in the original Notification Intent. Intent setup for this scenario is below:
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Is there an Action, Category or Flag I can set on an Intent that will emulate re-opening the app process as if done by the user but on a new Intent / Activity?
EDIT: To clarify the last question (although it seems my infant understanding of Android is failing me so it probably doesn't make sense): Is there an Action, Category or Flag I can set on an Intent, like in the snippet above, that will allow me to determine if the app process has been killed by the OS?
The easiest way to determine if Android has killed the process and then created a new process is as follows:
In your root Activity (the one with ACTION=MAIN and CATEGORY=DEFAULT) create a public static boolean variable like this:
public static boolean initialized;
in onCreate() of your root Activity, set this variable to true.
In onCreate() of all your other activities, you can check if Android has killed/recreated the task by checking the state of the boolean, and if the app hasn't been initialized, you can redirect to the root Activity or call an initialization method or whatever... like this:
if (!RootActivity.initialized) {
// Android has killed and recreated the process and launched this
// Activity. We need to reinitialize everything now
... redirect to root activity or call reinitialize method
}
Since the process id will change when app is being killed and restarted, you can use this to check it:
In onSaveInstanceState(Bundle outState) get current process id and save it in outState:
onSavedInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putInt("my_pid", Process.myPid());
}
Then in onCreate(Bunde savedInstanceState) compare the saved process id and the current process id:
onCreate(Bundle savedInstanceState){
if(savedInstanceState!=null){
if(savedInstanceState.getInt("my_pid",-1)==android.os.Process.myPid())
// app was not killed
else
// app was killed
}
}

create pop up instead of notification from BroadcastReceiver

i need to display pop up message in the center of screen , after reading in few forums i found out that it can be achieved by starting a new transparent activity .
this is my code :
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent();
i.setClassName(context,
"com.ui.activity.NotificationActivity");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
this work's but :
if application is in background than the application will come to the foreground with the pop up on it (if the application is not on background this work's great ).
If you don't want your app to be brought to the foreground when this popup is shown, you need to ensure that NotificationActivity has a different task affinity than the rest of your application.
In the manifest, add this to the <activity> definition for NotificationActivity:
android:taskAffinity=""
Without this, the default task affinity for all Activities is the same, so that when Android looks for a task to launch NotificationActivity into, and your app is already running, Android launches NotificationActivity into that task and brings it to the foreground.
If you change the task affinity, Android won't look for an existing task to launch NotificationActivity into, and will just create a new task.

Activity opened twice

I have an application that uses Urban Airship for push notification. When a notification arrives and the user clicks on it, activity A in my application should open and do something.
I've installed the BroadcastReceiver as is shown in the docs, and it's almost working.
When my app is in the foreground I don't let the user see the notification at all, and just handle it automatically.
When my app is not running at all, the activity opens up just fine.
When my app is in the background (which always happens when A is the top activity), a second instance of Activity A is created.
This is, of course, a problem. I don't want two A activities, I just want one of them. Here's the relevant BroadcastReceiver code:
#Override
public void onReceive(Context ctx, Intent intent)
{
Log.i(tag, "Push notification received: " + intent.toString());
String action = intent.getAction();
int notificationId = intent.getIntExtra(PushManager.EXTRA_NOTIFICATION_ID, -1);
if(action.equals(PushManager.ACTION_NOTIFICATION_OPENED))
{
Intent intentActivity = new Intent(ctx, ActivityA.class);
intentActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
UAirship.shared().getApplicationContext().startActivity((intentActivity);
}
}
UPDATE:
I tried to bypass this bug by calling System.exit(0) when the user presses Back on Activity A. The process ended, but then it was restarted immediately! My BroadcastReceiver is not called again in the second instance. What's happening?
UPDATE 2:
#codeMagic asked for more information about the app and activity A.
This app lets its user review certain items and comment on them. Activity A is started when the app is launched. If the user's session isn't valid any more, a Login activity is started. Once the user logs in, activity A becomes active again. A only has a "No items to review" message and a "Try now" button.
When the user logs in, the server starts sending push notifications whenever a new item is available for review. When the app gets the notification, activity A accesses the server and gets the next item to review. The item is shown in activity B. Once the review is submitted to the server, activity B finishes and activity A is again the top activity.
The server knows when a user is reviewing an item (because activity A fetched it), and doesn't send push notifications until the review is submitted - meaning a notification can't come if the user isn't logged in or if the user is viewing activity B.
While I agree there is a subtle race condition here, it is not causing the problem I'm seeing - in testing I am 100% positive there's no race condition - the push notification is only sent after Activity A becomes active again.
The solution was to add a launchMode='singleTask' to the activity in AndroidManifest.xml . As a result, instead of a new activity, onNewIntent of the same activity instance is called.
You can use one of several Intent Flags. FLAG_ACTIVITY_REORDER_TO_FRONT being one of them. This will bring the Activity to the front of the stack if it is already in the stack and if not then it will create a new instance. I believe you will still need FLAG_ACTIVITY_NEW_TASK if you aren't calling it from an Activity
Intent.FLAG_ACTIVITY_CLEAR_TOP should also work. But this will clear any other Activities on the stack. It just depends on what other functionality you need. Look through the Intent Flags and see which of these will work best for you
There are multiple scenarios when this could happen. One of them can be handled this way. Please see my answer here: https://stackoverflow.com/a/44117025/2959575
Ok, two notes on this :
You can register a broadcast receiver via the manifest so it is independent of any parts of your app. and use a Singleton pattern (keep a static reference to your activity somewhere in your app) that way you can check if their is an activity viewing or not and process accordingly.
// your activity A
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
myActivityReference = this;
}
public void onPause() {
super.onPause();
if (isFinishing()) {
myActivityReference = null;
}
}
or you can keep everything as it is and use activity lunching modes flags in your manifest such as singleTop, singleInstance ... etc. take a look here android activity lunch modes

Notification to restore a task rather than a specific activity?

I have a foreground service that keeps a connection open with the server as long as the user is logged into the application. This is so that the connection is kept alive and can receive messages directly from the server even when the application has been sent into the background by the user pressing Home.
The application has a number of Activities, any of which could be the active one when it is sent into the background.
I would like to allow the user to click on the notification to restore the current Activity. I understand how to restore a particular activity, but wondered if there is a way to restore the last Activity that the user was on? Of course I could keep track of the the last one, and then call that from the Notification callback, but thought there might be a way at a task level?
Thanks for any advice you can offer.
What you need is just a simple Activity that does nothing. Here is an example:
public class NotificationActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Now finish, which will drop the user in to the activity that was at the top
// of the task stack
finish();
}
}
Set up your notification to start this activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).
When the user selects this notification, if your application is running, then the NotificationActivity will be started on top of the topmost activity in your application's task and that task will be brought to the foreground. When the NotificationActivity finishes, it will simply return the user to the topmost activity in your application (ie: wherever the user left it when it went into the background).
This won't work if your application isn't already running. However, you have 2 options to deal with that:
Make sure the notification isn't present in the notification bar when your application is not running.
In the onCreate() method of the NotificationActivity, check if your application is running, and if it isn't running call startActivity() and launch your application. If you do this, be sure to set the flag Intent.FLAG_ACTIVITY_NEW_TASK when starting the application so that the root activity of the task is not NotificationActivity.
Works very well, thanks David! The following class checks if the application is already running and if not, starts it before finishing (as suggested by David in option 2).
public class NotificationActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// If this activity is the root activity of the task, the app is not running
if (isTaskRoot())
{
// Start the app before finishing
Intent startAppIntent = new Intent(getApplicationContext(), MainActivity.class);
startAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startAppIntent);
}
finish();
}
}
There is a simpler solution that does not require the extra activity. See this post for details. Basically, the notification starts the (possibly existing) task the same way it is started when you click the launcher icon while the app ist in the background.
My solution, which emulates the behaviour of the launcher (bringing up the task to the foreground):
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName(MyApplication.class.getPackage().getName(), MainActivity.class.getName());
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
This works, no doubts about it but the problem is when you set your intent as ACTION_MAIN. Then you will not be able to set any bundle to the intent. I mean, your primitive data will not be received from the target activity because ACTION_MAIN can not contain any extra data.
Instead of this, you can just set your activities as singleTask and call your intent normally without setting ACTION_MAIN and receive the intent from onNewIntent() method of your target activity.
But be aware if you call, super.onNewIntent(intent); then a second instance of the activity will be created. Just don't call super method.
I combined David Wasser's and Raginmari's solution by doing that approach to the root activity of your app then it will work for both cases when your app was already started or haven't been started.
public class YourRootActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (!isTaskRoot()) // checks if this root activity is at root, if not, we presented it from notification and we are resuming the app from previous open state
{
val extras = intent.extras // do stuffs with extras.
finish();
return;
}
// OtherWise start the app as usual
}
}

Categories

Resources