EDIT
In this answer, matdev brought to my attention the more modern approach to listening to app lifecycle events via ProcessLifeCycleOwner. See https://developer.android.com/topic/libraries/architecture/lifecycle
As such, to better organize the desired session management functionality, the following structure should be used. Register the SessionTracker in onCreate of the MyApplication. Functionality related to tracking user sessions is then sequestered to the SessionTracker class.
First add to your build.gradle
dependencies {
implementation "android.arch.lifecycle:extensions:1.1.1"
}
Then, implement the following:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(SessionTracker.getInstance());
}
}
public class SessionTracker implements LifecycleObserver {
private static SessionTracker sSessionTracker;
private static final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000; // Time allowed for transitions
private Timer mStopDelayTimer;
private TimerTask mActivityTransitionTimerTask;
private boolean mWasInBackground = true;
private AppSession mAppSession;
public static SessionTracker getInstance() {
if (sSessionTracker == null) {
sSessionTracker = new SessionTracker();
}
return sSessionTracker;
}
private SessionTracker() {
// no-op
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private void onLifecycleStop() {
submitAppSession(appSession);
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private void onLifecycleStart() {
mAppSession = new AppSession();
}
private void submitAppSession(AppSession appSession) {
// TODO submit app session here
}
}
public class AppSession {
/* TODO */
}
PREVIOUS ANSWER
The answer provided by d60402 here and the suggestion by Hanno Binder to register activity callbacks using Application.registerActivityLifecycleCallbacks() led me to this solution.
I extended Application and registered callbacks to Activity methods onPause and onStart as shown below. In these methods a timer is started/stopped (one activity being exited where onPause is called, a new one being entered where onStart is called). The flag "wasInBackground" is toggled when the app determined to be in the background/foreground (true/false). If the app was in the background when the onStart callback is run, "appEntered" is called. If the time passed between onPause and onStart callbacks is greater than a specified time (giving enough time for activity transitions) "appExited" is called where the app session is considered to be finished.
public class MyApplication extends Application {
public static final String LOG_TAG = "MyApp";
public boolean wasInBackground = true;
private AppSession appSession;
private Timer mActivityTransitionTimer;
private TimerTask mActivityTransitionTimerTask;
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000; // Time allowed for transitions
Application.ActivityLifecycleCallbacks activityCallbacks = new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(Activity activity) {
if (wasInBackground) {
//Do app-wide came-here-from-background code
appEntered();
}
stopActivityTransitionTimer();
}
@Override
public void onActivityPaused(Activity activity) {
startActivityTransitionTimer();
}
...
};
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(activityCallbacks);
}
public void startActivityTransitionTimer() {
this.mActivityTransitionTimer = new Timer();
this.mActivityTransitionTimerTask = new TimerTask() {
public void run() {
// Task is run when app is exited
wasInBackground = true;
appExited();
}
};
this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
MAX_ACTIVITY_TRANSITION_TIME_MS);
}
public void stopActivityTransitionTimer() {
if (this.mActivityTransitionTimerTask != null) {
this.mActivityTransitionTimerTask.cancel();
}
if (this.mActivityTransitionTimer != null) {
this.mActivityTransitionTimer.cancel();
}
this.wasInBackground = false;
}
private void appEntered() {
Log.i(LOG_TAG, "APP ENTERED");
appSession = new AppSession();
}
private void appExited() {
Log.i(LOG_TAG, "APP EXITED");
appSession.finishAppSession();
// Submit AppSession to server
submitAppSession(appSession);
long sessionLength = (appSession.getT_close() - appSession.getT_open())/1000L;
Log.i(LOG_TAG, "Session Length: " + sessionLength);
}