1

I want to send an event to firebase if user spend a minimum of 20sec on my app. This looks simple enough as I only have to create a timer. Here is what I've done so far:

Subscription sessionEvent = Observable.timer(20, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(aLong -> Log.v("tag", "hello"));

I am subscribing inside the onResume of my activity while unsubscribing inside onPause. This is however a problem since if my activity started another activity, onPause will also be called. That means that the timer will stop even though the user is still on my app.

What I'm looking for is when the user closed the app, that's when the timer will stop. I already tried using the onUserLeaveHint, unfortunately, starting another activity will also call that method.

hehe
  • 1,294
  • 13
  • 26

4 Answers4

2

You can use ActivityLifecycleCallbacks to monitor the state of all activities in your app. You can register one with Application.registerActivityLifecycleCallbacks() and it will be called for every Activity that goes through its lifecycle methods. When you have a total of 0 started activities, you can be sure that the user is not looking at your app, but it's also entirely possible that they might switch back in at any time from the task switcher.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
0

I dont think that there is a direct way to detect if your app is on foreground or background if you have multiple activities. However you can use OnResume and OnPause to track the state of each activity. Use boolean Shared Preferences to track the state of each activity and periodically run a background service using repeating alert to simply check if there is at least on activity on screen. StartTime-StartTime = duration.

0

Here is an example Application implementation that uses the ActivityLifeCycleCallbacks mentioned in Doug Stevenson's answer. The onTrimMemory() method is used to detect when the app is backgrounded:

public class MyApplication extends Application
        implements Application.ActivityLifecycleCallbacks {

    private static final String TAG = "MyApplication";

    private Handler handler;
    private final Runnable generateEvent = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "User engagement is 20 secs");
            // Generate analytics event
        }
    };

    private boolean mAppInForeground;

    @Override
    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(this);
        handler = new Handler(getMainLooper());
        mAppInForeground = false;
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            Log.i(TAG, "onTrimMemory: App in background");
            handler.removeCallbacks(generateEvent);
            mAppInForeground = false;
        }
    }

    @Override
    public void onActivityStarted(Activity activity) {
        // Called when any activity is started
        Log.d(TAG, "onActivityStarted: Was foreground=" + mAppInForeground);
        if (!mAppInForeground) {
            mAppInForeground = true;
            handler.postDelayed(generateEvent, 20 * 1000);
        }
    }

    // Remaining ActivityLifecycleCallbacks are not used
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
    @Override
    public void onActivityResumed(Activity activity) {}
    @Override
    public void onActivityPaused(Activity activity) {}
    @Override
    public void onActivityStopped(Activity activity) {}
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
    @Override
    public void onActivityDestroyed(Activity activity) {}
}
Bob Snyder
  • 37,759
  • 6
  • 111
  • 158
0

I ended up using <uses-permission android:name="android.permission.GET_TASKS"/> to check if the app is on foreground. here is the method that I used:

public boolean isAppForeground(Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

    if (am == null) return false;

    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP){
        List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();

        if (processInfos != null && !processInfos.isEmpty()) {
            ActivityManager.RunningAppProcessInfo info = processInfos.get(0);
            if (info == null || info.processName == null || !info.processName.contentEquals(context.getPackageName()))
                return false;
        } else {
            return false;
        }
    }
    else{
        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);

        if (tasks != null && !tasks.isEmpty()) {
            ComponentName topActivity = tasks.get(0).topActivity;
            if (topActivity == null || topActivity.getPackageName() == null || !topActivity.getPackageName().equals(context.getPackageName())) {
                return false;
            }
        } else {
            return false;
        }
    }

    return true;
}

The method is located in my custom Application class. I used registerActivityLifecycleCallbacks to check inside the onActivityPaused if the app is on foreground, I will not stop the timer, but if it's not, I will stop the timer.

hehe
  • 1,294
  • 13
  • 26