package com.malcom.library.android;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.activitylifecyclecallbackscompat.MalcomActivityLifecycleCallbacksCompat;
import com.malcom.library.android.module.core.MCMCoreAdapter;
import com.malcom.library.android.module.stats.MCMStats;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Keeps track of the opened activities to know when the user starts and finishes using the app
* to signal the start and end of a beacon.
*
* It understands that the user starts using the app (beacon start) when a new activity starts
* and the user is not in the middle of a session. And it understands that the user finishes
* using the app when an activity stops and there are no activities started for a little while.
*/
public class MalcomActivityLifecycleCallbacks implements MalcomActivityLifecycleCallbacksCompat
{
/** Number of activities that are started in any given moment */
private static AtomicInteger startedActivities = new AtomicInteger(0);
/** Wether the user is in the middle of a "session". We use it to know when to signal the start/end beacon. */
private static AtomicBoolean inTheMiddleOfASession = new AtomicBoolean(false);
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// Patch to fix a bug: https://github.com/MyMalcom/malcom-lib-android/issues/30
MCMStats.initContext(activity.getApplicationContext());
}
@Override
public void onActivityStarted(Activity activity)
{
int numActivities = startedActivities.incrementAndGet();
final boolean wasInTheMiddleOfASession = inTheMiddleOfASession.getAndSet(true);
if (Log.isLoggable(MCMDefines.LOG_TAG, Log.DEBUG))
Log.d(MCMDefines.LOG_TAG, "Activity started (total: " + numActivities + ", in session: " + wasInTheMiddleOfASession + ")");
if (!wasInTheMiddleOfASession) {
Log.d(MCMDefines.LOG_TAG, "An activity started and there's no current session: Malcom session starts");
MCMCoreAdapter.getInstance().moduleStatsStartBeacon(activity.getApplicationContext(),true);
}
}
@Override
public void onActivityResumed(Activity activity) {
// Nothing to do
}
@Override
public void onActivityPaused(Activity activity) {
// Nothing to do
}
@Override
public void onActivityStopped(Activity activity)
{
int numActivities = startedActivities.decrementAndGet();
if (Log.isLoggable(MCMDefines.LOG_TAG, Log.DEBUG))
Log.d(MCMDefines.LOG_TAG, "Activity ended (total: " + numActivities + ")");
signalEndBeaconIfNoActivitesStarted();
}
/**
* If no activities are started it signals the end beacon and sets {@link #inTheMiddleOfASession} to false
* (it is understood that the user has finished using the app for now).
*
* When a user changes from one activity to another there could be a lapse where there are no started
* activities but immediately there will be one that starts. That's why the number of started activities
* is rechecked after a little wait before actually signalling the end beacon.
*
* See: http://stackoverflow.com/questions/3287666/android-how-to-know-when-an-app-enters-or-the-background-mode/19502860
*/
private void signalEndBeaconIfNoActivitesStarted()
{
if (startedActivities.get() == 0)
{
new Thread()
{
@Override
public void run()
{
try {
Log.d(MCMDefines.LOG_TAG, "No activities started. Waiting a while...");
Thread.sleep(1000); // TODO: How much we should wait for a new activity to start?
} catch (InterruptedException e) {
Log.e(MCMDefines.LOG_TAG, "Unexpected interruption", e);
}
if (startedActivities.get() == 0)
{
inTheMiddleOfASession.getAndSet(false);
Log.d(MCMDefines.LOG_TAG, "No activities started for a while: Malcom session ends.");
MCMCoreAdapter.getInstance().moduleStatsEndBeacon();
}
else
Log.d(MCMDefines.LOG_TAG, "An activity started while waiting. Malcom session hasn't ended yet.");
}
}.start();
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
// Nothing to do
}
@Override
public void onActivityDestroyed(Activity activity) {
// Nothing to do (we can't count on this method being called)
}
}