Friday, May 31, 2013

Android: Solution to detect when an Android app goes to the background and come back to the foreground without getRunningTasks or getRunningAppProcesses



In Android, we don’t have options directly to find whether our app goes to background or not like applicationdidenterbackground in iOS.
I have gone through lot of articles and forums; everywhere I am finding only one result.

private boolean isApplicationBroughtToBackground() {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List tasks = am.getRunningTasks(1);
    if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        if (!topActivity.getPackageName().equals(context.getPackageName())) {
            return true;
        }
    }

    return false;
}

But we can’t use getRunningTasks because android specifies “method is only intended for debugging and presenting task management user interfaces“.

Solution to detect when an Android app goes to the background and come back to the foreground without getRunningTasks or getRunningAppProcesses by using onWindowFocusChanged and onStop method

The solution i tired for my application as shown below.

BaseActivity is a superclass of all the activities.

BaseActivity.java

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.widget.Toast;

/**
 * @author Harsha
 *
 *         BaseActivity class extends Activity
 */
public abstract class BaseActivity extends Activity {

protected static final String TAG = BaseActivity.class.getName();

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

public static boolean isAppWentToBg = false;

public static boolean isWindowFocused = false;

public static boolean isMenuOpened = false;

public static boolean isBackPressed = false;

@Override
protected void onStart() {
Log.d(TAG, "onStart isAppWentToBg " + isAppWentToBg);

applicationWillEnterForeground();

super.onStart();
}

private void applicationWillEnterForeground() {
if (isAppWentToBg) {
isAppWentToBg = false;
Toast.makeText(getApplicationContext(), "App is in foreground",
Toast.LENGTH_SHORT).show();
}
}

@Override
protected void onStop() {
super.onStop();

Log.d(TAG, "onStop ");
applicationdidenterbackground();
}

public void applicationdidenterbackground() {
if (!isWindowFocused) {
isAppWentToBg = true;
Toast.makeText(getApplicationContext(),
"App is Going to Background", Toast.LENGTH_SHORT).show();
}
}

@Override
public void onBackPressed() {

if (this instanceof MainActivity) {

} else {
isBackPressed = true;
}

Log.d(TAG,
"onBackPressed " + isBackPressed + ""
+ this.getLocalClassName());
super.onBackPressed();
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {

isWindowFocused = hasFocus;

if (isBackPressed && !hasFocus) {
isBackPressed = false;
isWindowFocused = true;
}

super.onWindowFocusChanged(hasFocus);
}

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
case R.id.action_settings:
Intent i = new Intent(this, SettingActivity.class);
startActivity(i);
break;
}
return true;
}

@Override
public boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
case R.id.action_settings:
Intent i = new Intent(this, SettingActivity.class);
startActivity(i);
break;
}
return true;
}

}

Each of the activity classes extendes BaseActivity class to track the application status.

MainActivity.java

import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;

public class MainActivity extends BaseActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

((ImageButton) findViewById(R.id.SecondBtn))
.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(),
SecondActvity.class));
}
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}

SecondActvity.java

import android.os.Bundle;

public class SecondActvity extends BaseActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
}

}
 App output looks as :




Improvements
If you want to see an example without having common base class, see app-foreground-n-background-using-callbacks-in-application. It also gives the Solution to detect when an Android app goes to the background and come back to the foreground without getRunningTasks or getRunningAppProcesses. This example supports from API level 14 and above because they interface classes used are added from API level 14.

Source Code
You can download the source code by clicking here: AppStatus-SourceCode.  This project is built using eclipse IDE. Unzip and import the project into Eclipse, it’s a good idea to use the Project by clean and rebuild from the project menu. It works in all API levels.


Thanks for reading :) 
Whether this post is helpful?
If you have any other quick thoughts/hints that you think people will find useful, feel free to leave a comment.  

25 comments :

  1. Correct me if I'm wrong (haven't actually tried running the app) ... but I think that on the first launch, "App is in foreground" will not be shown.

    isappWentToBg is initialized to 'false' and therefore the if-block in applicationWillEnterForeground() will not execute.

    This will be true whenever the process is killed (static value is lost).

    perhaps isAppWentToBg should be initialized to 'true'.

    ReplyDelete
    Replies
    1. You're right, my intention is not to call method initial stage. so the method won't be called when app launch or process killed also.
      Thanks for the information :)

      Delete
    2. I also want to get notified or show toast when app process is killed. How can I achieve it? Is it even possible?

      Delete
    3. If process is killed then app is killed too. So, we can't show the toast message.

      Delete
  2. I found if you lock screen by Power button or timeout, it will not show toast message.

    ReplyDelete
    Replies
    1. Thanks for the response, I haven't checked in the above mentioned case...
      On press of power button understood, but not timeout..what is timeout here?

      Delete
    2. What i can say is only simple and awesome..

      Delete
    3. I also want to show the correct toast on screen lock using power button. How can I implement it?

      Delete
    4. We can implement the feature by using PowerManager.

      Delete
  3. First, I don't understand why you do not reference the StackOverflow post where you took the code from.

    Second, I don't understand why you do not post directly your BaseActivity code which is what people will be looking for anyway when coming here.

    Regards.

    ReplyDelete
    Replies
    1. Hi forecemagic,

      Thanks for your comment.

      First, I haven't take any code from stackoverflow.
      second, I have given total source code in the blog.

      Regards
      Vardhan

      Delete
  4. Hi Vardhan,

    I have to do capture Applaunch and App minimize events for myapp for Appirater integration.

    Your code suits good. But i have a problem, i have many activities,fragments in Application.say some Activities extends FragmentActivity, activity and fragments. i have to show the appirater dialog after every 3rd launch of the app, No matter where he is present(fragment/activity/fragmentactivity)

    .How to handle this case. Because, it needs the architecture changes, if i need to extends all the activities by Base Activity suggested by you. Please help, how can i acheive this.


    Regards,

    Arvind Anandala

    ReplyDelete
    Replies
    1. Hi Arvind,

      Thanks for the response.

      I have two ideas to implement this scenario (I haven't implemented both)

      1. You can use a logic to detect and handle whether your in foreground or background in application class, So that no need of using base activity. (This can be achieved)

      2. ComponentCallbacks2 interface may help by checking onTrimMemory. ( I haven't tried this solution)


      Delete
  5. I think you can use ActivityLifeCycleCallbacks (ICS onwards) to do this better.
    http://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks.html

    ReplyDelete
    Replies
    1. Hi karthik,

      Thanks for the comment.
      By using ActivityLifeCycleCallbacks, we can get all the life cycles of activity @ one place but not the status of the app whether it went to background or foreground but we can use this callbacks and also can trigger the solution for app status.

      Regards
      Harsha

      Delete
  6. Hi there,

    I just wanted to say that this post was helpful. Thank you! That's all :)

    Regards

    ReplyDelete
  7. Hi, I am using your logic here for checking if my application has gone to backgound but I am facing some issues.

    1. Here the API "onWindowFocusChanged(boolean hasFocus)" has been used to check if the BaseActivity (all app activities are derived from here) has focus or not.

    2. However, this function is called twice during each transition from Activity A to Activity B; once when hasFocus = false, and once when hasFocus = true.

    3. Hence it is trying to reset mTimesStamp each time and Activity is transitioned.

    I would like to manipulate the APIs "onWindowFocusChanged" and "onStop" of BaseActivity,to only reset "mTimeStamp" when app goes to background. In rest of the cases, it should pass.

    Please let me know if this approach can be used to attain my objective

    ReplyDelete
    Replies
    1. You can have mTimeStamp variable in applicationdidenterbackground right. If you want to know when ur application went to background?

      Delete
  8. Fit perfect for my project! Thank you for sharing!

    ReplyDelete
  9. Thank you for sharing Vhardan, this post helped me a lot of to create my app.

    ReplyDelete
  10. I found an issue, please follow the steps to recreate it.

    1. Open any app in the device.
    2. Press "Home" button to minimize the app and then open your own app. (Toast shows up)
    3. Press "Menu" button, the one which shows thumbnail of you running apps and allow you to switch apps.
    4. Go to any other app. (Toast not show up) <-- should show up
    5. Press "Home" button
    6. Go back to your own app (Toast not show up) <-- should show up

    ReplyDelete
  11. Why onBackPressed() is used?

    ReplyDelete
  12. I don't feel like you can rely on the absoluteness of this answer. This will only work if the system calls onWindowFocusChanged before calling onStop. However the documentation makes it clear that onWindowFocusChanged is independent of the Activity lifecycle:

    "Note that this provides information about global focus state, which is managed independently of activity lifecycles."

    http://developer.android.com/reference/android/app/Activity.html#onWindowFocusChanged(boolean)

    Additionally, setting up menu items in your abstract base activity isn't the best idea. All Activities will be required to have those menu items (you may not want a home menu item on your Main Activity).

    ReplyDelete
  13. You have provided an nice article, Thank you very much for this one. And i hope this will be useful for many people.. and i am waiting for your next post keep on updating these kinds of knowledgeable things...

    Mobile App Development Company in Chennai
    Android app Development Company in Chennai
    ios app development Company in Chennai

    ReplyDelete