From 91f572d069c544e06bf24088edf8e4114df6d66e Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Tue, 1 Oct 2024 17:25:52 -0400 Subject: [PATCH] refactor: Migrate Internal Application context class to kotlin --- .../mparticle/internal/AppStateManager.java | 495 ------------------ .../com/mparticle/internal/AppStateManager.kt | 490 +++++++++++++++++ .../internal/ApplicationContextWrapper.java | 338 ------------ .../internal/ApplicationContextWrapper.kt | 351 +++++++++++++ .../mparticle/internal/AppStateManagerTest.kt | 20 +- .../internal/KitFrameworkWrapperTest.kt | 2 +- 6 files changed, 852 insertions(+), 844 deletions(-) delete mode 100644 android-core/src/main/java/com/mparticle/internal/AppStateManager.java create mode 100644 android-core/src/main/java/com/mparticle/internal/AppStateManager.kt delete mode 100644 android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.java create mode 100644 android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.kt diff --git a/android-core/src/main/java/com/mparticle/internal/AppStateManager.java b/android-core/src/main/java/com/mparticle/internal/AppStateManager.java deleted file mode 100644 index 6a483b4e7..000000000 --- a/android-core/src/main/java/com/mparticle/internal/AppStateManager.java +++ /dev/null @@ -1,495 +0,0 @@ -package com.mparticle.internal; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.Application; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.SystemClock; - -import androidx.annotation.Nullable; - -import com.mparticle.MPEvent; -import com.mparticle.MParticle; -import com.mparticle.identity.IdentityApi; -import com.mparticle.identity.IdentityApiRequest; -import com.mparticle.identity.MParticleUser; -import com.mparticle.internal.listeners.InternalListenerManager; - -import org.json.JSONObject; - -import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - - -/** - * This class is responsible for maintaining the session state by listening to the Activity lifecycle. - */ -public class AppStateManager { - - private ConfigManager mConfigManager; - Context mContext; - private final SharedPreferences mPreferences; - private InternalSession mCurrentSession = new InternalSession(); - private WeakReference mCurrentActivityReference = null; - - private String mCurrentActivityName; - /** - * This boolean is important in determining if the app is running due to the user opening the app, - * or if we're running due to the reception of a Intent such as an FCM message. - */ - public static boolean mInitialized; - - AtomicLong mLastStoppedTime; - /** - * it can take some time between when an activity stops and when a new one (or the same one on a configuration change/rotation) - * starts again, so use this handler and ACTIVITY_DELAY to determine when we're *really" in the background - */ - Handler delayedBackgroundCheckHandler = new Handler(); - static final long ACTIVITY_DELAY = 1000; - - - /** - * Some providers need to know for the given session, how many 'interruptions' there were - how many - * times did the user leave and return prior to the session timing out. - */ - AtomicInteger mInterruptionCount = new AtomicInteger(0); - - /** - * Constants used by the messaging/push framework to describe the app state when various - * interactions occur (receive/show/tap). - */ - public static final String APP_STATE_FOREGROUND = "foreground"; - public static final String APP_STATE_BACKGROUND = "background"; - public static final String APP_STATE_NOTRUNNING = "not_running"; - - /** - * Important to determine foreground-time length for a given session. - * Uses the system-uptime clock to avoid devices which wonky clocks, or clocks - * that change while the app is running. - */ - private long mLastForegroundTime; - - boolean mUnitTesting = false; - private MessageManager mMessageManager; - private Uri mLaunchUri; - private String mLaunchAction; - - public AppStateManager(Context context, boolean unitTesting) { - mUnitTesting = unitTesting; - mContext = context.getApplicationContext(); - mLastStoppedTime = new AtomicLong(getTime()); - mPreferences = context.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE); - ConfigManager.addMpIdChangeListener(new IdentityApi.MpIdChangeListener() { - @Override - public void onMpIdChanged(long newMpid, long previousMpid) { - if (mCurrentSession != null) { - mCurrentSession.addMpid(newMpid); - } - } - }); - } - - public AppStateManager(Context context) { - this(context, false); - } - - public void init(int apiVersion) { - if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - setupLifecycleCallbacks(); - } - } - - public String getLaunchAction() { - return mLaunchAction; - } - - public Uri getLaunchUri() { - return mLaunchUri; - } - - public void setConfigManager(ConfigManager manager) { - mConfigManager = manager; - } - - public void setMessageManager(MessageManager manager) { - mMessageManager = manager; - } - - private long getTime() { - if (mUnitTesting) { - return System.currentTimeMillis(); - } else { - return SystemClock.elapsedRealtime(); - } - } - - public void onActivityResumed(Activity activity) { - try { - mCurrentActivityName = AppStateManager.getActivityName(activity); - - int interruptions = mInterruptionCount.get(); - if (!mInitialized || !getSession().isActive()) { - mInterruptionCount = new AtomicInteger(0); - } - String previousSessionPackage = null; - String previousSessionUri = null; - String previousSessionParameters = null; - if (activity != null) { - ComponentName callingApplication = activity.getCallingActivity(); - if (callingApplication != null) { - previousSessionPackage = callingApplication.getPackageName(); - } - if (activity.getIntent() != null) { - previousSessionUri = activity.getIntent().getDataString(); - if (mLaunchUri == null) { - mLaunchUri = activity.getIntent().getData(); - } - if (mLaunchAction == null) { - mLaunchAction = activity.getIntent().getAction(); - } - if (activity.getIntent().getExtras() != null && activity.getIntent().getExtras().getBundle(Constants.External.APPLINK_KEY) != null) { - JSONObject parameters = new JSONObject(); - try { - parameters.put(Constants.External.APPLINK_KEY, MPUtility.wrapExtras(activity.getIntent().getExtras().getBundle(Constants.External.APPLINK_KEY))); - } catch (Exception e) { - - } - previousSessionParameters = parameters.toString(); - } - } - } - - mCurrentSession.updateBackgroundTime(mLastStoppedTime, getTime()); - - boolean isBackToForeground = false; - if (!mInitialized) { - initialize(mCurrentActivityName, previousSessionUri, previousSessionParameters, previousSessionPackage); - } else if (isBackgrounded() && mLastStoppedTime.get() > 0) { - isBackToForeground = true; - mMessageManager.postToMessageThread(new CheckAdIdRunnable(mConfigManager)); - logStateTransition(Constants.StateTransitionType.STATE_TRANS_FORE, - mCurrentActivityName, - mLastStoppedTime.get() - mLastForegroundTime, - getTime() - mLastStoppedTime.get(), - previousSessionUri, - previousSessionParameters, - previousSessionPackage, - interruptions); - } - mLastForegroundTime = getTime(); - - if (mCurrentActivityReference != null) { - mCurrentActivityReference.clear(); - mCurrentActivityReference = null; - } - mCurrentActivityReference = new WeakReference(activity); - - MParticle instance = MParticle.getInstance(); - if (instance != null) { - if (instance.isAutoTrackingEnabled()) { - instance.logScreen(mCurrentActivityName); - } - if (isBackToForeground) { - instance.Internal().getKitManager().onApplicationForeground(); - Logger.debug("App foregrounded."); - } - instance.Internal().getKitManager().onActivityResumed(activity); - } - } catch (Exception e) { - Logger.verbose("Failed while trying to track activity resume: " + e.getMessage()); - } - } - - public void onActivityPaused(Activity activity) { - try { - mPreferences.edit().putBoolean(Constants.PrefKeys.CRASHED_IN_FOREGROUND, false).apply(); - mLastStoppedTime = new AtomicLong(getTime()); - if (mCurrentActivityReference != null && activity == mCurrentActivityReference.get()) { - mCurrentActivityReference.clear(); - mCurrentActivityReference = null; - } - - delayedBackgroundCheckHandler.postDelayed(new Runnable() { - @Override - public void run() { - try { - if (isBackgrounded()) { - checkSessionTimeout(); - logBackgrounded(); - mConfigManager.setPreviousAdId(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }, ACTIVITY_DELAY); - - MParticle instance = MParticle.getInstance(); - if (instance != null) { - if (instance.isAutoTrackingEnabled()) { - instance.logScreen( - new MPEvent.Builder(AppStateManager.getActivityName(activity)) - .internalNavigationDirection(false) - .build() - ); - } - instance.Internal().getKitManager().onActivityPaused(activity); - } - } catch (Exception e) { - Logger.verbose("Failed while trying to track activity pause: " + e.getMessage()); - } - } - - public void ensureActiveSession() { - if (!mInitialized) { - initialize(null, null, null, null); - } - InternalSession session = getSession(); - session.mLastEventTime = System.currentTimeMillis(); - if (!session.isActive()) { - newSession(); - } else { - mMessageManager.updateSessionEnd(getSession()); - } - } - - void logStateTransition(String transitionType, String currentActivity, long previousForegroundTime, long suspendedTime, String dataString, String launchParameters, String launchPackage, int interruptions) { - if (mConfigManager.isEnabled()) { - ensureActiveSession(); - mMessageManager.logStateTransition(transitionType, - currentActivity, - dataString, - launchParameters, - launchPackage, - previousForegroundTime, - suspendedTime, - interruptions - ); - } - } - - public void logStateTransition(String transitionType, String currentActivity) { - logStateTransition(transitionType, currentActivity, 0, 0, null, null, null, 0); - } - - /** - * Creates a new session and generates the start-session message. - */ - private void newSession() { - startSession(); - mMessageManager.startSession(mCurrentSession); - Logger.debug("Started new session"); - mMessageManager.startUploadLoop(); - enableLocationTracking(); - checkSessionTimeout(); - } - - private void enableLocationTracking() { - if (mPreferences.contains(Constants.PrefKeys.LOCATION_PROVIDER)) { - String provider = mPreferences.getString(Constants.PrefKeys.LOCATION_PROVIDER, null); - long minTime = mPreferences.getLong(Constants.PrefKeys.LOCATION_MINTIME, 0); - long minDistance = mPreferences.getLong(Constants.PrefKeys.LOCATION_MINDISTANCE, 0); - if (provider != null && minTime > 0 && minDistance > 0) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.enableLocationTracking(provider, minTime, minDistance); - } - } - } - } - - boolean shouldEndSession() { - InternalSession session = getSession(); - MParticle instance = MParticle.getInstance(); - return 0 != session.mSessionStartTime && - isBackgrounded() - && session.isTimedOut(mConfigManager.getSessionTimeout()) - && (instance == null || !instance.Media().getAudioPlaying()); - } - - private void checkSessionTimeout() { - delayedBackgroundCheckHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (shouldEndSession()) { - Logger.debug("Session timed out"); - endSession(); - } - } - }, mConfigManager.getSessionTimeout()); - } - - private void initialize(String currentActivityName, String previousSessionUri, String previousSessionParameters, String previousSessionPackage) { - mInitialized = true; - logStateTransition(Constants.StateTransitionType.STATE_TRANS_INIT, - currentActivityName, - 0, - 0, - previousSessionUri, - previousSessionParameters, - previousSessionPackage, - 0); - } - - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onActivityCreated(activity, savedInstanceState); - } - } - - public void onActivityStarted(Activity activity) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onActivityStarted(activity); - } - } - - public void onActivityStopped(Activity activity) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onActivityStopped(activity); - } - } - - private void logBackgrounded() { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - logStateTransition(Constants.StateTransitionType.STATE_TRANS_BG, mCurrentActivityName); - instance.Internal().getKitManager().onApplicationBackground(); - mCurrentActivityName = null; - Logger.debug("App backgrounded."); - mInterruptionCount.incrementAndGet(); - } - } - - @TargetApi(14) - private void setupLifecycleCallbacks() { - ((Application) mContext).registerActivityLifecycleCallbacks(new MPLifecycleCallbackDelegate(this)); - } - - public boolean isBackgrounded() { - return !mInitialized || (mCurrentActivityReference == null && (getTime() - mLastStoppedTime.get() >= ACTIVITY_DELAY)); - } - - private static String getActivityName(Activity activity) { - return activity.getClass().getCanonicalName(); - } - - public String getCurrentActivityName() { - return mCurrentActivityName; - } - - public InternalSession getSession() { - return mCurrentSession; - } - - public void endSession() { - Logger.debug("Ended session"); - mMessageManager.endSession(mCurrentSession); - disableLocationTracking(); - mCurrentSession = new InternalSession(); - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onSessionEnd(); - } - InternalListenerManager.getListener().onSessionUpdated(mCurrentSession); - } - - private void disableLocationTracking() { - SharedPreferences.Editor editor = mPreferences.edit(); - editor.remove(Constants.PrefKeys.LOCATION_PROVIDER) - .remove(Constants.PrefKeys.LOCATION_MINTIME) - .remove(Constants.PrefKeys.LOCATION_MINDISTANCE) - .apply(); - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.disableLocationTracking(); - } - } - - public void startSession() { - mCurrentSession = new InternalSession().start(mContext); - mLastStoppedTime = new AtomicLong(getTime()); - enableLocationTracking(); - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onSessionStart(); - } - } - - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onActivitySaveInstanceState(activity, outState); - } - } - - public void onActivityDestroyed(Activity activity) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().onActivityDestroyed(activity); - } - } - - public WeakReference getCurrentActivity() { - return mCurrentActivityReference; - } - - static class CheckAdIdRunnable implements Runnable { - ConfigManager configManager; - - CheckAdIdRunnable(@Nullable ConfigManager configManager) { - this.configManager = configManager; - } - - @Override - public void run() { - MPUtility.AdIdInfo adIdInfo = MPUtility.getAdIdInfo(MParticle.getInstance().Internal().getAppStateManager().mContext); - String currentAdId = (adIdInfo == null ? null : (adIdInfo.isLimitAdTrackingEnabled ? null : adIdInfo.id)); - String previousAdId = configManager.getPreviousAdId(); - if (currentAdId != null && !currentAdId.equals(previousAdId)) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - MParticleUser user = instance.Identity().getCurrentUser(); - if (user != null) { - instance.Identity().modify(new Builder(user) - .googleAdId(currentAdId, previousAdId) - .build()); - } else { - instance.Identity().addIdentityStateListener(new IdentityApi.SingleUserIdentificationCallback() { - @Override - public void onUserFound(MParticleUser user) { - instance.Identity().modify(new Builder(user) - .googleAdId(currentAdId, previousAdId) - .build()); - } - }); - } - } - } - } - } - - static class Builder extends IdentityApiRequest.Builder { - Builder(MParticleUser user) { - super(user); - } - - Builder() { - super(); - } - - @Override - protected IdentityApiRequest.Builder googleAdId(String newGoogleAdId, String oldGoogleAdId) { - return super.googleAdId(newGoogleAdId, oldGoogleAdId); - } - } -} diff --git a/android-core/src/main/java/com/mparticle/internal/AppStateManager.kt b/android-core/src/main/java/com/mparticle/internal/AppStateManager.kt new file mode 100644 index 000000000..66646a019 --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/AppStateManager.kt @@ -0,0 +1,490 @@ +package com.mparticle.internal + +import android.annotation.TargetApi +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import com.mparticle.MPEvent +import com.mparticle.MParticle +import com.mparticle.identity.IdentityApi.SingleUserIdentificationCallback +import com.mparticle.identity.IdentityApiRequest +import com.mparticle.identity.MParticleUser +import com.mparticle.internal.listeners.InternalListenerManager +import org.json.JSONObject +import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong + +/** + * This class is responsible for maintaining the session state by listening to the Activity lifecycle. + */ +open class AppStateManager @JvmOverloads constructor( + context: Context, + unitTesting: Boolean = false +) { + private var mConfigManager: ConfigManager? = null + var mContext: Context + private val mPreferences: SharedPreferences + open var session: InternalSession = InternalSession() + + var currentActivity: WeakReference? = null + private set + + var currentActivityName: String? = null + private set + var mLastStoppedTime: AtomicLong + + /** + * it can take some time between when an activity stops and when a new one (or the same one on a configuration change/rotation) + * starts again, so use this handler and ACTIVITY_DELAY to determine when we're *really" in the background + */ + @JvmField + var delayedBackgroundCheckHandler: Handler = Handler(Looper.getMainLooper()) + + /** + * Some providers need to know for the given session, how many 'interruptions' there were - how many + * times did the user leave and return prior to the session timing out. + */ + var mInterruptionCount: AtomicInteger = AtomicInteger(0) + + /** + * Important to determine foreground-time length for a given session. + * Uses the system-uptime clock to avoid devices which wonky clocks, or clocks + * that change while the app is running. + */ + private var mLastForegroundTime: Long = 0 + + var mUnitTesting: Boolean = false + private var mMessageManager: MessageManager? = null + var launchUri: Uri? = null + private set + var launchAction: String? = null + private set + + init { + mUnitTesting = unitTesting + mContext = context.applicationContext + mLastStoppedTime = AtomicLong(time) + mPreferences = context.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE) + ConfigManager.addMpIdChangeListener { newMpid, previousMpid -> + if (session != null) { + session.addMpid(newMpid) + } + } + } + + fun init(apiVersion: Int) { + if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + setupLifecycleCallbacks() + } + } + + fun setConfigManager(manager: ConfigManager?) { + mConfigManager = manager + } + + fun setMessageManager(manager: MessageManager?) { + mMessageManager = manager + } + + private val time: Long + get() = if (mUnitTesting) { + System.currentTimeMillis() + } else { + SystemClock.elapsedRealtime() + } + + fun onActivityResumed(activity: Activity?) { + try { + currentActivityName = getActivityName(activity) + + val interruptions = mInterruptionCount.get() + if (!mInitialized || !session.isActive) { + mInterruptionCount = AtomicInteger(0) + } + var previousSessionPackage: String? = null + var previousSessionUri: String? = null + var previousSessionParameters: String? = null + if (activity != null) { + val callingApplication = activity.callingActivity + if (callingApplication != null) { + previousSessionPackage = callingApplication.packageName + } + if (activity.intent != null) { + previousSessionUri = activity.intent.dataString + if (launchUri == null) { + launchUri = activity.intent.data + } + if (launchAction == null) { + launchAction = activity.intent.action + } + if (activity.intent.extras != null && activity.intent.extras!! + .getBundle(Constants.External.APPLINK_KEY) != null + ) { + val parameters = JSONObject() + try { + parameters.put( + Constants.External.APPLINK_KEY, + MPUtility.wrapExtras( + activity.intent.extras!!.getBundle(Constants.External.APPLINK_KEY) + ) + ) + } catch (e: Exception) { + Logger.error("Exception on onActivityResumed ") + } + previousSessionParameters = parameters.toString() + } + } + } + + session!!.updateBackgroundTime(mLastStoppedTime, time) + + var isBackToForeground = false + if (!mInitialized) { + initialize( + currentActivityName, + previousSessionUri, + previousSessionParameters, + previousSessionPackage + ) + } else if (isBackgrounded() && mLastStoppedTime.get() > 0) { + isBackToForeground = true + mMessageManager!!.postToMessageThread(CheckAdIdRunnable(mConfigManager)) + logStateTransition( + Constants.StateTransitionType.STATE_TRANS_FORE, + currentActivityName, + mLastStoppedTime.get() - mLastForegroundTime, + time - mLastStoppedTime.get(), + previousSessionUri, + previousSessionParameters, + previousSessionPackage, + interruptions + ) + } + mLastForegroundTime = time + + if (currentActivity != null) { + currentActivity!!.clear() + currentActivity = null + } + currentActivity = WeakReference(activity) + + val instance = MParticle.getInstance() + if (instance != null) { + if (instance.isAutoTrackingEnabled) { + instance.logScreen(currentActivityName!!) + } + if (isBackToForeground) { + instance.Internal().kitManager.onApplicationForeground() + Logger.debug("App foregrounded.") + } + instance.Internal().kitManager.onActivityResumed(activity) + } + } catch (e: Exception) { + Logger.verbose("Failed while trying to track activity resume: " + e.message) + } + } + + fun onActivityPaused(activity: Activity) { + try { + mPreferences.edit().putBoolean(Constants.PrefKeys.CRASHED_IN_FOREGROUND, false).apply() + mLastStoppedTime = AtomicLong(time) + if (currentActivity != null && activity === currentActivity!!.get()) { + currentActivity!!.clear() + currentActivity = null + } + + delayedBackgroundCheckHandler.postDelayed( + { + try { + if (isBackgrounded()) { + checkSessionTimeout() + logBackgrounded() + mConfigManager!!.setPreviousAdId() + } + } catch (e: Exception) { + e.printStackTrace() + } + }, + ACTIVITY_DELAY + ) + + val instance = MParticle.getInstance() + if (instance != null) { + if (instance.isAutoTrackingEnabled) { + instance.logScreen( + MPEvent.Builder(getActivityName(activity)) + .internalNavigationDirection(false) + .build() + ) + } + instance.Internal().kitManager.onActivityPaused(activity) + } + } catch (e: Exception) { + Logger.verbose("Failed while trying to track activity pause: " + e.message) + } + } + + fun ensureActiveSession() { + if (!mInitialized) { + initialize(null, null, null, null) + } + val session = session + session!!.mLastEventTime = System.currentTimeMillis() + if (!session.isActive) { + newSession() + } else { + mMessageManager!!.updateSessionEnd(this.session) + } + } + + fun logStateTransition( + transitionType: String?, + currentActivity: String?, + previousForegroundTime: Long, + suspendedTime: Long, + dataString: String?, + launchParameters: String?, + launchPackage: String?, + interruptions: Int + ) { + if (mConfigManager!!.isEnabled) { + ensureActiveSession() + mMessageManager!!.logStateTransition( + transitionType, + currentActivity, + dataString, + launchParameters, + launchPackage, + previousForegroundTime, + suspendedTime, + interruptions + ) + } + } + + fun logStateTransition(transitionType: String?, currentActivity: String?) { + logStateTransition(transitionType, currentActivity, 0, 0, null, null, null, 0) + } + + /** + * Creates a new session and generates the start-session message. + */ + private fun newSession() { + startSession() + mMessageManager!!.startSession(session) + Logger.debug("Started new session") + mMessageManager!!.startUploadLoop() + enableLocationTracking() + checkSessionTimeout() + } + + private fun enableLocationTracking() { + if (mPreferences.contains(Constants.PrefKeys.LOCATION_PROVIDER)) { + val provider = mPreferences.getString(Constants.PrefKeys.LOCATION_PROVIDER, null) + val minTime = mPreferences.getLong(Constants.PrefKeys.LOCATION_MINTIME, 0) + val minDistance = mPreferences.getLong(Constants.PrefKeys.LOCATION_MINDISTANCE, 0) + if (provider != null && minTime > 0 && minDistance > 0) { + val instance = MParticle.getInstance() + instance?.enableLocationTracking(provider, minTime, minDistance) + } + } + } + + fun shouldEndSession(): Boolean { + val session = session + val instance = MParticle.getInstance() + return ( + 0L != session!!.mSessionStartTime && + isBackgrounded() && + session.isTimedOut(mConfigManager!!.sessionTimeout) && + (instance == null || !instance.Media().audioPlaying) + ) + } + + private fun checkSessionTimeout() { + delayedBackgroundCheckHandler.postDelayed({ + if (shouldEndSession()) { + Logger.debug("Session timed out") + endSession() + } + }, mConfigManager!!.sessionTimeout.toLong()) + } + + private fun initialize( + currentActivityName: String?, + previousSessionUri: String?, + previousSessionParameters: String?, + previousSessionPackage: String? + ) { + mInitialized = true + logStateTransition( + Constants.StateTransitionType.STATE_TRANS_INIT, + currentActivityName, + 0, + 0, + previousSessionUri, + previousSessionParameters, + previousSessionPackage, + 0 + ) + } + + fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onActivityCreated(activity, savedInstanceState) + } + + fun onActivityStarted(activity: Activity?) { + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onActivityStarted(activity) + } + + fun onActivityStopped(activity: Activity?) { + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onActivityStopped(activity) + } + + private fun logBackgrounded() { + val instance = MParticle.getInstance() + if (instance != null) { + logStateTransition(Constants.StateTransitionType.STATE_TRANS_BG, currentActivityName) + instance.Internal().kitManager.onApplicationBackground() + currentActivityName = null + Logger.debug("App backgrounded.") + mInterruptionCount.incrementAndGet() + } + } + + @TargetApi(14) + private fun setupLifecycleCallbacks() { + (mContext as Application).registerActivityLifecycleCallbacks( + MPLifecycleCallbackDelegate( + this + ) + ) + } + + open fun isBackgrounded(): Boolean { + return !mInitialized || (currentActivity == null && (time - mLastStoppedTime.get() >= ACTIVITY_DELAY)) + } + + open fun fetchSession(): InternalSession { + return session + } + + fun endSession() { + Logger.debug("Ended session") + mMessageManager!!.endSession(session) + disableLocationTracking() + session = InternalSession() + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onSessionEnd() + InternalListenerManager.getListener().onSessionUpdated(session) + } + + private fun disableLocationTracking() { + val editor = mPreferences.edit() + editor.remove(Constants.PrefKeys.LOCATION_PROVIDER) + .remove(Constants.PrefKeys.LOCATION_MINTIME) + .remove(Constants.PrefKeys.LOCATION_MINDISTANCE) + .apply() + val instance = MParticle.getInstance() + instance?.disableLocationTracking() + } + + fun startSession() { + session = InternalSession().start(mContext) + mLastStoppedTime = AtomicLong(time) + enableLocationTracking() + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onSessionStart() + } + + fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onActivitySaveInstanceState(activity, outState) + } + + fun onActivityDestroyed(activity: Activity?) { + val instance = MParticle.getInstance() + instance?.Internal()?.kitManager?.onActivityDestroyed(activity) + } + + internal class CheckAdIdRunnable(var configManager: ConfigManager?) : Runnable { + override fun run() { + val adIdInfo = + MPUtility.getAdIdInfo(MParticle.getInstance()!!.Internal().appStateManager.mContext) + val currentAdId = + (if (adIdInfo == null) null else (if (adIdInfo.isLimitAdTrackingEnabled) null else adIdInfo.id)) + val previousAdId = configManager!!.previousAdId + if (currentAdId != null && currentAdId != previousAdId) { + val instance = MParticle.getInstance() + if (instance != null) { + val user = instance.Identity().currentUser + if (user != null) { + instance.Identity().modify( + Builder(user) + .googleAdId(currentAdId, previousAdId) + .build() + ) + } else { + instance.Identity() + .addIdentityStateListener(object : SingleUserIdentificationCallback() { + override fun onUserFound(user: MParticleUser) { + instance.Identity().modify( + Builder(user) + .googleAdId(currentAdId, previousAdId) + .build() + ) + } + }) + } + } + } + } + } + + internal class Builder : IdentityApiRequest.Builder { + constructor(user: MParticleUser?) : super(user) + + constructor() : super() + + public override fun googleAdId( + newGoogleAdId: String?, + oldGoogleAdId: String? + ): IdentityApiRequest.Builder { + return super.googleAdId(newGoogleAdId, oldGoogleAdId) + } + } + + companion object { + /** + * This boolean is important in determining if the app is running due to the user opening the app, + * or if we're running due to the reception of a Intent such as an FCM message. + */ + @JvmField + var mInitialized: Boolean = false + + const val ACTIVITY_DELAY: Long = 1000 + + /** + * Constants used by the messaging/push framework to describe the app state when various + * interactions occur (receive/show/tap). + */ + const val APP_STATE_FOREGROUND: String = "foreground" + const val APP_STATE_BACKGROUND: String = "background" + const val APP_STATE_NOTRUNNING: String = "not_running" + + private fun getActivityName(activity: Activity?): String { + return activity!!.javaClass.canonicalName + } + } +} diff --git a/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.java b/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.java deleted file mode 100644 index 5cb0cb466..000000000 --- a/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.java +++ /dev/null @@ -1,338 +0,0 @@ -package com.mparticle.internal; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.Application; -import android.content.ComponentCallbacks; -import android.content.Context; -import android.content.res.Configuration; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; - -import com.mparticle.MParticle; - -import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -public class ApplicationContextWrapper extends Application { - private Application mBaseApplication; - private boolean mReplay = true; - private boolean mRecord = true; - private ActivityLifecycleCallbackRecorder mActivityLifecycleCallbackRecorder; - - enum MethodType {ON_CREATED, ON_STARTED, ON_RESUMED, ON_PAUSED, ON_STOPPED, ON_SAVE_INSTANCE_STATE, ON_DESTROYED} - - ; - - public ApplicationContextWrapper(Application application) { - mBaseApplication = application; - attachBaseContext(mBaseApplication); - mActivityLifecycleCallbackRecorder = new ActivityLifecycleCallbackRecorder(); - startRecordLifecycles(); - } - - public void setReplayActivityLifecycle(boolean replay) { - this.mReplay = replay; - } - - public boolean isReplayActivityLifecycle() { - return mReplay; - } - - public void setRecordActivityLifecycle(boolean record) { - if (this.mRecord = record) { - startRecordLifecycles(); - } else { - stopRecordLifecycles(); - } - } - - public void setActivityLifecycleCallbackRecorder(ActivityLifecycleCallbackRecorder activityLifecycleCallbackRecorder) { - mActivityLifecycleCallbackRecorder = activityLifecycleCallbackRecorder; - } - - public boolean isRecordActivityLifecycle() { - return mRecord; - } - - @SuppressLint("MissingSuperCall") - @Override - public void onCreate() { - mBaseApplication.onCreate(); - } - - @SuppressLint("MissingSuperCall") - @Override - public void onTerminate() { - mBaseApplication.onTerminate(); - } - - @SuppressLint("MissingSuperCall") - @Override - public void onConfigurationChanged(Configuration newConfig) { - mBaseApplication.onConfigurationChanged(newConfig); - } - - @SuppressLint("MissingSuperCall") - @Override - public void onLowMemory() { - mBaseApplication.onLowMemory(); - } - - @SuppressLint("MissingSuperCall") - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void onTrimMemory(int level) { - mBaseApplication.onTrimMemory(level); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void registerComponentCallbacks(ComponentCallbacks callback) { - mBaseApplication.registerComponentCallbacks(callback); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void unregisterComponentCallbacks(ComponentCallbacks callback) { - mBaseApplication.unregisterComponentCallbacks(callback); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void registerActivityLifecycleCallbacks(final ActivityLifecycleCallbacks callback) { - registerActivityLifecycleCallbacks(callback, false); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - void registerActivityLifecycleCallbacks(final ActivityLifecycleCallbacks callback, boolean unitTesting) { - mBaseApplication.registerActivityLifecycleCallbacks(callback); - ReplayLifecycleCallbacksRunnable runnable = new ReplayLifecycleCallbacksRunnable(callback); - if (unitTesting) { - runnable.run(); - } else { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - new Handler().post(runnable); - } - } - - @Override - public Context getApplicationContext() { - return this; - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) { - mBaseApplication.unregisterActivityLifecycleCallbacks(callback); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - @Override - public void registerOnProvideAssistDataListener(OnProvideAssistDataListener callback) { - mBaseApplication.registerOnProvideAssistDataListener(callback); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - @Override - public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) { - mBaseApplication.unregisterOnProvideAssistDataListener(callback); - } - - @Override - public int hashCode() { - return mBaseApplication.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return mBaseApplication.equals(obj); - } - - @Override - public String toString() { - return mBaseApplication.toString(); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - private void startRecordLifecycles() { - stopRecordLifecycles(); - mBaseApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallbackRecorder); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - public void stopRecordLifecycles() { - mBaseApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbackRecorder); - } - - public ActivityLifecycleCallbackRecorder getActivityLifecycleCallbackRecorderInstance() { - return new ActivityLifecycleCallbackRecorder(); - } - - public LifeCycleEvent getLifeCycleEventInstance(MethodType methodType, WeakReference activityRef) { - return new LifeCycleEvent(methodType, activityRef); - } - - public LifeCycleEvent getLifeCycleEventInstance(MethodType methodType, WeakReference activityRef, Bundle bundle) { - return new LifeCycleEvent(methodType, activityRef, bundle); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - class ActivityLifecycleCallbackRecorder implements ActivityLifecycleCallbacks { - List lifeCycleEvents = Collections.synchronizedList(new LinkedList()); - int MAX_LIST_SIZE = 10; - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_CREATED, new WeakReference(activity), savedInstanceState)); - } - - @Override - public void onActivityStarted(Activity activity) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_STARTED, new WeakReference(activity))); - } - - @Override - public void onActivityResumed(Activity activity) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_RESUMED, new WeakReference(activity))); - } - - @Override - public void onActivityPaused(Activity activity) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_PAUSED, new WeakReference(activity))); - } - - @Override - public void onActivityStopped(Activity activity) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_STOPPED, new WeakReference(activity))); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_SAVE_INSTANCE_STATE, new WeakReference(activity), outState)); - } - - @Override - public void onActivityDestroyed(Activity activity) { - getRecordedLifecycleList().add(new LifeCycleEvent(MethodType.ON_DESTROYED, new WeakReference(activity))); - } - - private List getRecordedLifecycleList() { - if (lifeCycleEvents.size() > MAX_LIST_SIZE) { - lifeCycleEvents.remove(0); - return getRecordedLifecycleList(); - } - return lifeCycleEvents; - } - - private LinkedList getRecordedLifecycleListCopy() { - LinkedList list; - synchronized (lifeCycleEvents) { - list = new LinkedList(lifeCycleEvents); - } - return list; - } - } - - class LifeCycleEvent { - private MethodType methodType; - private WeakReference activityRef; - private Bundle bundle; - - public LifeCycleEvent(MethodType methodType, WeakReference activityRef) { - this(methodType, activityRef, null); - } - - LifeCycleEvent(MethodType methodType, WeakReference activityRef, Bundle bundle) { - this.methodType = methodType; - this.activityRef = activityRef; - this.bundle = bundle; - } - - @Override - public boolean equals(Object o) { - if (o instanceof LifeCycleEvent) { - LifeCycleEvent l = (LifeCycleEvent) o; - boolean matchingActivityRef = false; - if (l.activityRef == null && activityRef == null) { - matchingActivityRef = true; - } else if (l.activityRef != null && activityRef != null) { - matchingActivityRef = l.activityRef.get() == activityRef.get(); - } - return matchingActivityRef && - l.methodType == methodType && - l.bundle == bundle; - } - return false; - } - } - - class ReplayLifecycleCallbacksRunnable implements Runnable { - ActivityLifecycleCallbacks callback; - - ReplayLifecycleCallbacksRunnable(ActivityLifecycleCallbacks callback) { - this.callback = callback; - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @Override - public void run() { - if (callback != null && mActivityLifecycleCallbackRecorder != null && mReplay) { - WeakReference reference = MParticle.getInstance().Internal().getKitManager() == null ? null : MParticle.getInstance().Internal().getKitManager().getCurrentActivity(); - if (reference != null) { - Activity currentActivity = reference.get(); - if (currentActivity != null) { - LinkedList recordedLifecycleList = mActivityLifecycleCallbackRecorder.getRecordedLifecycleListCopy(); - while (recordedLifecycleList.size() > 0) { - LifeCycleEvent lifeCycleEvent = recordedLifecycleList.removeFirst(); - if (lifeCycleEvent.activityRef != null) { - Activity recordedActivity = lifeCycleEvent.activityRef.get(); - if (recordedActivity != null) { - if (recordedActivity == currentActivity) { - switch (lifeCycleEvent.methodType) { - case ON_CREATED: - Logger.debug("Forwarding OnCreate"); - callback.onActivityCreated(recordedActivity, lifeCycleEvent.bundle); - break; - case ON_STARTED: - Logger.debug("Forwarding OnStart"); - callback.onActivityStarted(recordedActivity); - break; - case ON_RESUMED: - Logger.debug("Forwarding OnResume"); - callback.onActivityResumed(recordedActivity); - break; - case ON_PAUSED: - Logger.debug("Forwarding OnPause"); - callback.onActivityPaused(recordedActivity); - break; - case ON_SAVE_INSTANCE_STATE: - Logger.debug("Forwarding OnSaveInstance"); - callback.onActivitySaveInstanceState(recordedActivity, lifeCycleEvent.bundle); - break; - case ON_STOPPED: - Logger.debug("Forwarding OnStop"); - callback.onActivityStopped(recordedActivity); - break; - case ON_DESTROYED: - Logger.debug("Forwarding OnDestroy"); - callback.onActivityDestroyed(recordedActivity); - break; - } - } - } - } - } - } - } - } - } - } -} diff --git a/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.kt b/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.kt new file mode 100644 index 000000000..6b20037a1 --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/ApplicationContextWrapper.kt @@ -0,0 +1,351 @@ +package com.mparticle.internal + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.app.Activity +import android.app.Application +import android.content.ComponentCallbacks +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import com.mparticle.MParticle +import java.lang.ref.WeakReference +import java.util.Collections +import java.util.LinkedList + +open class ApplicationContextWrapper(private val mBaseApplication: Application) : Application() { + var isReplayActivityLifecycle: Boolean = true + private var mRecord = true + private var mActivityLifecycleCallbackRecorder: ActivityLifecycleCallbackRecorder? + + enum class MethodType { + ON_CREATED, ON_STARTED, ON_RESUMED, ON_PAUSED, ON_STOPPED, ON_SAVE_INSTANCE_STATE, ON_DESTROYED + } + + init { + attachBaseContext(mBaseApplication) + mActivityLifecycleCallbackRecorder = ActivityLifecycleCallbackRecorder() + startRecordLifecycles() + } + + fun setActivityLifecycleCallbackRecorder(activityLifecycleCallbackRecorder: ActivityLifecycleCallbackRecorder?) { + mActivityLifecycleCallbackRecorder = activityLifecycleCallbackRecorder + } + + var isRecordActivityLifecycle: Boolean + get() = mRecord + set(record) { + if (record.also { this.mRecord = it }) { + startRecordLifecycles() + } else { + stopRecordLifecycles() + } + } + + @SuppressLint("MissingSuperCall") + override fun onCreate() { + mBaseApplication.onCreate() + } + + @SuppressLint("MissingSuperCall") + override fun onTerminate() { + mBaseApplication.onTerminate() + } + + @SuppressLint("MissingSuperCall") + override fun onConfigurationChanged(newConfig: Configuration) { + mBaseApplication.onConfigurationChanged(newConfig) + } + + @SuppressLint("MissingSuperCall") + override fun onLowMemory() { + mBaseApplication.onLowMemory() + } + + @SuppressLint("MissingSuperCall") + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun onTrimMemory(level: Int) { + mBaseApplication.onTrimMemory(level) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun registerComponentCallbacks(callback: ComponentCallbacks) { + mBaseApplication.registerComponentCallbacks(callback) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun unregisterComponentCallbacks(callback: ComponentCallbacks) { + mBaseApplication.unregisterComponentCallbacks(callback) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun registerActivityLifecycleCallbacks(callback: ActivityLifecycleCallbacks) { + registerActivityLifecycleCallbacks(callback, false) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + fun registerActivityLifecycleCallbacks( + callback: ActivityLifecycleCallbacks?, + unitTesting: Boolean + ) { + mBaseApplication.registerActivityLifecycleCallbacks(callback) + val runnable = ReplayLifecycleCallbacksRunnable(callback) + if (unitTesting) { + runnable.run() + } else { + if (Looper.myLooper() == null) { + Looper.prepare() + } + Handler().post(runnable) + } + } + + override fun getApplicationContext(): Context { + return this + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun unregisterActivityLifecycleCallbacks(callback: ActivityLifecycleCallbacks) { + mBaseApplication.unregisterActivityLifecycleCallbacks(callback) + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + override fun registerOnProvideAssistDataListener(callback: OnProvideAssistDataListener) { + mBaseApplication.registerOnProvideAssistDataListener(callback) + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + override fun unregisterOnProvideAssistDataListener(callback: OnProvideAssistDataListener) { + mBaseApplication.unregisterOnProvideAssistDataListener(callback) + } + + override fun hashCode(): Int { + return mBaseApplication.hashCode() + } + + override fun equals(obj: Any?): Boolean { + return mBaseApplication == obj + } + + override fun toString(): String { + return mBaseApplication.toString() + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + private fun startRecordLifecycles() { + stopRecordLifecycles() + mBaseApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallbackRecorder) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + fun stopRecordLifecycles() { + mBaseApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbackRecorder) + } + + val activityLifecycleCallbackRecorderInstance: ActivityLifecycleCallbackRecorder + get() = ActivityLifecycleCallbackRecorder() + + fun getLifeCycleEventInstance( + methodType: MethodType, + activityRef: WeakReference? + ): LifeCycleEvent { + return LifeCycleEvent(methodType, activityRef) + } + + fun getLifeCycleEventInstance( + methodType: MethodType, + activityRef: WeakReference?, + bundle: Bundle? + ): LifeCycleEvent { + return LifeCycleEvent(methodType, activityRef, bundle) + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + inner class ActivityLifecycleCallbackRecorder : ActivityLifecycleCallbacks { + var lifeCycleEvents: MutableList = + Collections.synchronizedList(LinkedList()) + var MAX_LIST_SIZE: Int = 10 + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_CREATED, + WeakReference(activity), + savedInstanceState + ) + ) + } + + override fun onActivityStarted(activity: Activity) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_STARTED, + WeakReference(activity) + ) + ) + } + + override fun onActivityResumed(activity: Activity) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_RESUMED, + WeakReference(activity) + ) + ) + } + + override fun onActivityPaused(activity: Activity) { + recordedLifecycleList.add(LifeCycleEvent(MethodType.ON_PAUSED, WeakReference(activity))) + } + + override fun onActivityStopped(activity: Activity) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_STOPPED, + WeakReference(activity) + ) + ) + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_SAVE_INSTANCE_STATE, + WeakReference(activity), + outState + ) + ) + } + + override fun onActivityDestroyed(activity: Activity) { + recordedLifecycleList.add( + LifeCycleEvent( + MethodType.ON_DESTROYED, + WeakReference(activity) + ) + ) + } + + private val recordedLifecycleList: MutableList + get() { + if (lifeCycleEvents.size > MAX_LIST_SIZE) { + lifeCycleEvents.removeAt(0) + return recordedLifecycleList + } + return lifeCycleEvents + } + + internal val recordedLifecycleListCopy: LinkedList + get() { + var list: LinkedList + synchronized(lifeCycleEvents) { + list = LinkedList(lifeCycleEvents) + } + return list + } + } + + inner class LifeCycleEvent( + val methodType: MethodType, + val activityRef: WeakReference?, + val bundle: Bundle? + ) { + constructor( + methodType: MethodType, + activityRef: WeakReference? + ) : this(methodType, activityRef, null) + + override fun equals(o: Any?): Boolean { + if (o is LifeCycleEvent) { + val l = o + var matchingActivityRef = false + if (l.activityRef == null && activityRef == null) { + matchingActivityRef = true + } else if (l.activityRef != null && activityRef != null) { + matchingActivityRef = l.activityRef.get() === activityRef.get() + } + return matchingActivityRef && l.methodType == methodType && l.bundle == bundle + } + return false + } + } + + internal inner class ReplayLifecycleCallbacksRunnable(var callback: ActivityLifecycleCallbacks?) : + Runnable { + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + override fun run() { + if (callback != null && mActivityLifecycleCallbackRecorder != null && isReplayActivityLifecycle) { + val reference = if (MParticle.getInstance()!! + .Internal().kitManager == null + ) { + null + } else { + MParticle.getInstance()!! + .Internal().kitManager.currentActivity + } + if (reference != null) { + val currentActivity = reference.get() + if (currentActivity != null) { + val recordedLifecycleList: LinkedList = + mActivityLifecycleCallbackRecorder!!.recordedLifecycleListCopy + while (recordedLifecycleList.size > 0) { + val lifeCycleEvent = recordedLifecycleList.removeFirst() + if (lifeCycleEvent.activityRef != null) { + val recordedActivity = lifeCycleEvent.activityRef.get() + if (recordedActivity != null) { + if (recordedActivity === currentActivity) { + when (lifeCycleEvent.methodType) { + MethodType.ON_CREATED -> { + Logger.debug("Forwarding OnCreate") + callback!!.onActivityCreated( + recordedActivity, + lifeCycleEvent.bundle + ) + } + + MethodType.ON_STARTED -> { + Logger.debug("Forwarding OnStart") + callback!!.onActivityStarted(recordedActivity) + } + + MethodType.ON_RESUMED -> { + Logger.debug("Forwarding OnResume") + callback!!.onActivityResumed(recordedActivity) + } + + MethodType.ON_PAUSED -> { + Logger.debug("Forwarding OnPause") + callback!!.onActivityPaused(recordedActivity) + } + + MethodType.ON_SAVE_INSTANCE_STATE -> { + Logger.debug("Forwarding OnSaveInstance") + callback!!.onActivitySaveInstanceState( + recordedActivity, + lifeCycleEvent.bundle!! + ) + } + + MethodType.ON_STOPPED -> { + Logger.debug("Forwarding OnStop") + callback!!.onActivityStopped(recordedActivity) + } + + MethodType.ON_DESTROYED -> { + Logger.debug("Forwarding OnDestroy") + callback!!.onActivityDestroyed(recordedActivity) + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/android-core/src/test/kotlin/com/mparticle/internal/AppStateManagerTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/AppStateManagerTest.kt index d45ed8908..d0d670847 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/AppStateManagerTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/AppStateManagerTest.kt @@ -28,7 +28,7 @@ class AppStateManagerTest { fun setup() { val context = MockContext() mockContext = context.applicationContext as MockApplication - manager = AppStateManager(mockContext, true) + manager = AppStateManager(mockContext!!, true) prefs = mockContext?.getSharedPreferences(null, 0) as MockSharedPreferences val configManager = Mockito.mock(ConfigManager::class.java) manager.setConfigManager(configManager) @@ -53,7 +53,7 @@ class AppStateManagerTest { @Test @Throws(Exception::class) fun testOnActivityStarted() { - Assert.assertEquals(true, manager.isBackgrounded) + Assert.assertEquals(true, manager.isBackgrounded()) manager.onActivityStarted(activity) Mockito.verify(MParticle.getInstance()!!.Internal().kitManager, Mockito.times(1)) .onActivityStarted(activity) @@ -62,10 +62,10 @@ class AppStateManagerTest { @Test @Throws(Exception::class) fun testOnActivityResumed() { - Assert.assertEquals(true, manager.isBackgrounded) + Assert.assertEquals(true, manager.isBackgrounded()) manager.onActivityResumed(activity) Assert.assertTrue(AppStateManager.mInitialized) - Assert.assertEquals(false, manager.isBackgrounded) + Assert.assertEquals(false, manager.isBackgrounded()) manager.onActivityResumed(activity) } @@ -124,7 +124,7 @@ class AppStateManagerTest { fun testSecondActivityStart() { manager.onActivityPaused(activity) Thread.sleep(1000) - Assert.assertEquals(true, manager.isBackgrounded) + Assert.assertEquals(true, manager.isBackgrounded()) manager.onActivityResumed(activity) val activity2 = Mockito.mock( Activity::class.java @@ -135,20 +135,20 @@ class AppStateManagerTest { manager.onActivityPaused(activity2) manager.onActivityPaused(activity3) Thread.sleep(1000) - Assert.assertEquals(false, manager.isBackgrounded) + Assert.assertEquals(false, manager.isBackgrounded()) manager.onActivityPaused(activity) Thread.sleep(1000) - Assert.assertEquals(true, manager.isBackgrounded) + Assert.assertEquals(true, manager.isBackgrounded()) } @Test @Throws(Exception::class) fun testOnActivityPaused() { manager.onActivityResumed(activity) - Assert.assertEquals(false, manager.isBackgrounded) + Assert.assertEquals(false, manager.isBackgrounded()) manager.onActivityPaused(activity) Thread.sleep(1000) - Assert.assertEquals(true, manager.isBackgrounded) + Assert.assertEquals(true, manager.isBackgrounded()) Assert.assertTrue(AppStateManager.mInitialized) Assert.assertTrue(manager.mLastStoppedTime.get() > 0) manager.onActivityResumed(activity) @@ -190,7 +190,7 @@ class AppStateManagerTest { return isBackground.value } - override fun getSession(): InternalSession { + override fun fetchSession(): InternalSession { return session.value!! } } diff --git a/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt index 84e969512..b61387976 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt @@ -576,7 +576,7 @@ class KitFrameworkWrapperTest { val mockIntegrationAttributes2 = randomUtils.getRandomAttributes(5) Mockito.`when`(mockAppStateManager.launchUri).thenReturn(mockLaunchUri) Mockito.`when`(mockAppStateManager.currentActivity).thenReturn(WeakReference(mockActivity)) - Mockito.`when`(mockAppStateManager.isBackgrounded).thenReturn(isBackground) + Mockito.`when`(mockAppStateManager.isBackgrounded()).thenReturn(isBackground) Mockito.`when`(mockConfigManager.latestKitConfiguration).thenReturn(mockKitConfiguration) Mockito.`when`(mockConfigManager.pushInstanceId).thenReturn(mockPushInstanceId) Mockito.`when`(mockConfigManager.pushSenderId).thenReturn(mockPushSenderId)