Blame | Last modification | View Log | RSS feed
package com.facebook;import android.os.Bundle;import android.text.format.DateUtils;import com.facebook.internal.Logger;import java.io.Serializable;class FacebookTimeSpentData implements Serializable {// Constantsprivate static final long serialVersionUID = 1L;private static final String TAG = AppEventsLogger.class.getCanonicalName();private static final long FIRST_TIME_LOAD_RESUME_TIME = -1;private static final long INTERRUPTION_THRESHOLD_MILLISECONDS = 1000;private static final long NUM_MILLISECONDS_IDLE_TO_BE_NEW_SESSION =60 * DateUtils.SECOND_IN_MILLIS;private static final long APP_ACTIVATE_SUPPRESSION_PERIOD_IN_MILLISECONDS =5 * DateUtils.MINUTE_IN_MILLIS;// Should be kept in sync with the iOS sdkprivate static final long[] INACTIVE_SECONDS_QUANTA =new long[] {5 * DateUtils.MINUTE_IN_MILLIS,15 * DateUtils.MINUTE_IN_MILLIS,30 * DateUtils.MINUTE_IN_MILLIS,1 * DateUtils.HOUR_IN_MILLIS,6 * DateUtils.HOUR_IN_MILLIS,12 * DateUtils.HOUR_IN_MILLIS,1 * DateUtils.DAY_IN_MILLIS,2 * DateUtils.DAY_IN_MILLIS,3 * DateUtils.DAY_IN_MILLIS,7 * DateUtils.DAY_IN_MILLIS,14 * DateUtils.DAY_IN_MILLIS,21 * DateUtils.DAY_IN_MILLIS,28 * DateUtils.DAY_IN_MILLIS,60 * DateUtils.DAY_IN_MILLIS,90 * DateUtils.DAY_IN_MILLIS,120 * DateUtils.DAY_IN_MILLIS,150 * DateUtils.DAY_IN_MILLIS,180 * DateUtils.DAY_IN_MILLIS,365 * DateUtils.DAY_IN_MILLIS,};private boolean isWarmLaunch;private boolean isAppActive;private long lastActivateEventLoggedTime;// Member data that's persisted to diskprivate long lastResumeTime;private long lastSuspendTime;private long millisecondsSpentInSession;private int interruptionCount;private String firstOpenSourceApplication;/*** Serialization proxy for the FacebookTimeSpentData class. This is version 1 of* serialization. Future serializations may differ in format. This* class should not be modified. If serializations formats change,* create a new class SerializationProxyVx.*/private static class SerializationProxyV1 implements Serializable {private static final long serialVersionUID = 6L;private final long lastResumeTime;private final long lastSuspendTime;private final long millisecondsSpentInSession;private final int interruptionCount;SerializationProxyV1(long lastResumeTime,long lastSuspendTime,long millisecondsSpentInSession,int interruptionCount) {this.lastResumeTime = lastResumeTime;this.lastSuspendTime = lastSuspendTime;this.millisecondsSpentInSession = millisecondsSpentInSession;this.interruptionCount = interruptionCount;}private Object readResolve() {return new FacebookTimeSpentData(lastResumeTime,lastSuspendTime,millisecondsSpentInSession,interruptionCount);}}/*** Constructor to be used for V1 serialization only, DO NOT CHANGE.*/private FacebookTimeSpentData(long lastResumeTime,long lastSuspendTime,long millisecondsSpentInSession,int interruptionCount) {resetSession();this.lastResumeTime = lastResumeTime;this.lastSuspendTime = lastSuspendTime;this.millisecondsSpentInSession = millisecondsSpentInSession;this.interruptionCount = interruptionCount;}/*** Serialization proxy for the FacebookTimeSpentData class. This is version 2 of* serialization. Future serializations may differ in format. This* class should not be modified. If serializations formats change,* create a new class SerializationProxyVx.*/private static class SerializationProxyV2 implements Serializable {private static final long serialVersionUID = 6L;private final long lastResumeTime;private final long lastSuspendTime;private final long millisecondsSpentInSession;private final int interruptionCount;private final String firstOpenSourceApplication;SerializationProxyV2(long lastResumeTime,long lastSuspendTime,long millisecondsSpentInSession,int interruptionCount,String firstOpenSourceApplication) {this.lastResumeTime = lastResumeTime;this.lastSuspendTime = lastSuspendTime;this.millisecondsSpentInSession = millisecondsSpentInSession;this.interruptionCount = interruptionCount;this.firstOpenSourceApplication = firstOpenSourceApplication;}private Object readResolve() {return new FacebookTimeSpentData(lastResumeTime,lastSuspendTime,millisecondsSpentInSession,interruptionCount,firstOpenSourceApplication);}}FacebookTimeSpentData() {resetSession();}/*** Constructor to be used for V2 serialization only, DO NOT CHANGE.*/private FacebookTimeSpentData(long lastResumeTime,long lastSuspendTime,long millisecondsSpentInSession,int interruptionCount,String firstOpenSourceApplication) {resetSession();this.lastResumeTime = lastResumeTime;this.lastSuspendTime = lastSuspendTime;this.millisecondsSpentInSession = millisecondsSpentInSession;this.interruptionCount = interruptionCount;this.firstOpenSourceApplication = firstOpenSourceApplication;}private Object writeReplace() {return new SerializationProxyV2(lastResumeTime,lastSuspendTime,millisecondsSpentInSession,interruptionCount,firstOpenSourceApplication);}void onSuspend(AppEventsLogger logger, long eventTime) {if (!isAppActive) {Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Suspend for inactive app");return;}long now = eventTime;long delta = (now - lastResumeTime);if (delta < 0) {Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Clock skew detected");delta = 0;}millisecondsSpentInSession += delta;lastSuspendTime = now;isAppActive = false;}void onResume(AppEventsLogger logger, long eventTime, String sourceApplicationInfo) {long now = eventTime;// Retain old behavior for activated app event - log the event if the event hasn't// been logged in the previous suppression interval or this is a cold launch.// If this is a cold launch, always log the event. Otherwise, use the last// event log time to determine if the app activate should be suppressed or not.if (isColdLaunch() ||((now - lastActivateEventLoggedTime) > APP_ACTIVATE_SUPPRESSION_PERIOD_IN_MILLISECONDS)) {Bundle eventParams = new Bundle();eventParams.putString(AppEventsConstants.EVENT_PARAM_SOURCE_APPLICATION,sourceApplicationInfo);logger.logEvent(AppEventsConstants.EVENT_NAME_ACTIVATED_APP, eventParams);lastActivateEventLoggedTime = now;}// If this is an application that's not calling onSuspend yet, log and return. We can't// track time spent for this application as there are no calls to onSuspend.if (isAppActive) {Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Resume for active app");return;}long interruptionDurationMillis = wasSuspendedEver() ? now - lastSuspendTime : 0;if (interruptionDurationMillis < 0) {Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Clock skew detected");interruptionDurationMillis = 0;}// If interruption duration is > new session threshold, then log old session// event and start a new session.if (interruptionDurationMillis > NUM_MILLISECONDS_IDLE_TO_BE_NEW_SESSION) {logAppDeactivatedEvent(logger, interruptionDurationMillis);} else {// We're not logging this resume event - check to see if this should count// as an interruptionif (interruptionDurationMillis > INTERRUPTION_THRESHOLD_MILLISECONDS) {interruptionCount++;}}// Set source application only for the first resume of the timespent session.if (interruptionCount == 0) {firstOpenSourceApplication = sourceApplicationInfo;}lastResumeTime = now;isAppActive = true;}private void logAppDeactivatedEvent(AppEventsLogger logger,long interruptionDurationMillis) {// Log the old session information and clear the dataBundle eventParams = new Bundle();eventParams.putInt(AppEventsConstants.EVENT_NAME_SESSION_INTERRUPTIONS,interruptionCount);eventParams.putString(AppEventsConstants.EVENT_NAME_TIME_BETWEEN_SESSIONS,String.format("session_quanta_%d", getQuantaIndex(interruptionDurationMillis)));eventParams.putString(AppEventsConstants.EVENT_PARAM_SOURCE_APPLICATION,firstOpenSourceApplication);logger.logEvent(AppEventsConstants.EVENT_NAME_DEACTIVATED_APP,(millisecondsSpentInSession/DateUtils.SECOND_IN_MILLIS),eventParams);resetSession();}private static int getQuantaIndex(long timeBetweenSessions) {int quantaIndex = 0;while (quantaIndex < INACTIVE_SECONDS_QUANTA.length &&INACTIVE_SECONDS_QUANTA[quantaIndex] < timeBetweenSessions) {++quantaIndex;}return quantaIndex;}private void resetSession() {isAppActive = false;lastResumeTime = FIRST_TIME_LOAD_RESUME_TIME;lastSuspendTime = FIRST_TIME_LOAD_RESUME_TIME;interruptionCount = 0;millisecondsSpentInSession = 0;}private boolean wasSuspendedEver() {return lastSuspendTime != FIRST_TIME_LOAD_RESUME_TIME;}private boolean isColdLaunch() {// On the very first call in the process lifecycle, this will always// return true. After that, it will always return false.boolean result = !isWarmLaunch;isWarmLaunch = true;return result;}}