Skip to content

Commit

Permalink
feat: add android context plugin (#14)
Browse files Browse the repository at this point in the history
* feat: add android context plugin
  • Loading branch information
qingzhuozhen authored Mar 22, 2022
1 parent 49229ac commit 25b6da3
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 15 deletions.
8 changes: 4 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ android {
}

dependencies {
implementation project(':common')
implementation project(':common-android')
implementation project(':id')
implementation project(':core')
api project(':common')
api project(':common-android')
api project(':id')
api project(':core')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

Expand Down
137 changes: 136 additions & 1 deletion android/src/main/java/com/amplitude/android/Amplitude.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,154 @@
package com.amplitude.android

import android.content.Context
import com.amplitude.android.plugins.AndroidContextPlugin
import com.amplitude.android.plugins.AndroidLifecyclePlugin
import com.amplitude.core.Amplitude
import com.amplitude.core.Storage
import com.amplitude.core.platform.plugins.AmplitudeDestination
import com.amplitude.core.utilities.AnalyticsIdentityListener
import com.amplitude.core.utilities.FileStorage
import com.amplitude.id.FileIdentityStorageProvider
import com.amplitude.id.IdentityConfiguration
import com.amplitude.id.IdentityContainer
import kotlinx.coroutines.launch

open class Amplitude(
configuration: Configuration
) : Amplitude(configuration) {

private var inForeground = false
var sessionId: Long = -1
private set
private var lastEventId: Long = -1
private var lastEventTime: Long = -1
private var previousSessionId: Long = -1

override fun build() {
idContainer = IdentityContainer.getInstance(IdentityConfiguration(instanceName = configuration.instanceName, apiKey = configuration.apiKey, identityStorageProvider = FileIdentityStorageProvider()))
val storageDirectory = (configuration as Configuration).context.getDir("${FileStorage.STORAGE_PREFIX}-${configuration.instanceName}", Context.MODE_PRIVATE)
idContainer = IdentityContainer.getInstance(
IdentityConfiguration(
instanceName = configuration.instanceName,
apiKey = configuration.apiKey,
identityStorageProvider = FileIdentityStorageProvider(),
storageDirectory = storageDirectory
)
)
idContainer.identityManager.addIdentityListener(AnalyticsIdentityListener(store))
amplitudeScope.launch(amplitudeDispatcher) {
previousSessionId = storage.read(Storage.Constants.PREVIOUS_SESSION_ID) ?.let {
it.toLong()
} ?: -1

add(AndroidContextPlugin())
}
add(AmplitudeDestination())
add(AndroidLifecyclePlugin())
}

fun onEnterForeground(timestamp: Long) {
amplitudeScope.launch(amplitudeDispatcher) {
startNewSessionIfNeeded(timestamp)
inForeground = true
}
}

fun onExitForeground(timestamp: Long) {
amplitudeScope.launch(amplitudeDispatcher) {
inForeground = false
if ((configuration as Configuration).flushEventsOnClose) {
flush()
}
storage.write(Storage.Constants.PREVIOUS_SESSION_ID, sessionId.toString())
storage.write(Storage.Constants.LAST_EVENT_TIME, lastEventTime.toString())
}
}

fun startNewSessionIfNeeded(timestamp: Long): Boolean {
if (inSession()) {

if (isWithinMinTimeBetweenSessions(timestamp)) {
refreshSessionTime(timestamp)
return false
}

startNewSession(timestamp)
return true
}

// no current session - check for previous session
if (isWithinMinTimeBetweenSessions(timestamp)) {
if (previousSessionId == -1L) {
startNewSession(timestamp)
return true
}

// extend previous session
setSessionId(previousSessionId)
refreshSessionTime(timestamp)
return false
}

startNewSession(timestamp)
return true
}

private fun setSessionId(timestamp: Long) {
sessionId = timestamp
previousSessionId = timestamp
amplitudeScope.launch(amplitudeDispatcher) {
storage.write(Storage.Constants.PREVIOUS_SESSION_ID, timestamp.toString())
}
}

private fun startNewSession(timestamp: Long) {
// end previous session
if ((configuration as Configuration).trackingSessionEvents) {
sendSessionEvent(END_SESSION_EVENT)
}

// start new session
setSessionId(timestamp)
refreshSessionTime(timestamp)
if ((configuration as Configuration).trackingSessionEvents) {
sendSessionEvent(START_SESSION_EVENT)
}
}

private fun sendSessionEvent(sessionEvent: String) {
if (!inSession()) {
return
}
track(sessionEvent)
}

fun refreshSessionTime(timestamp: Long) {
if (!inSession()) {
return
}
lastEventTime = timestamp
amplitudeScope.launch(amplitudeDispatcher) {
storage.write(Storage.Constants.LAST_EVENT_TIME, timestamp.toString())
}
}

private fun isWithinMinTimeBetweenSessions(timestamp: Long): Boolean {
val sessionLimit: Long = (configuration as Configuration).minTimeBetweenSessionsMillis
return timestamp - lastEventTime < sessionLimit
}

private fun inSession(): Boolean {
return sessionId >= 0
}

companion object {
/**
* The event type for start session events.
*/
const val START_SESSION_EVENT = "session_start"
/**
* The event type for end session events.
*/
const val END_SESSION_EVENT = "session_end"
}
}
20 changes: 15 additions & 5 deletions android/src/main/java/com/amplitude/android/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.amplitude.core.utilities.FileStorageProvider

class Configuration(
apiKey: String,
context: Context,
val context: Context,
flushQueueSize: Int = FLUSH_QUEUE_SIZE,
flushIntervalMillis: Int = FLUSH_INTERVAL_MILLIS,
instanceName: String = DEFAULT_INSTANCE,
Expand All @@ -19,7 +19,17 @@ class Configuration(
loggerProvider: LoggerProvider = AndroidLoggerProvider(),
minIdLength: Int? = null,
callback: ((BaseEvent) -> Unit)? = null,
useAdvertisingIdForDeviceId: Boolean = false,
useAppSetIdForDeviceId: Boolean = false,
enableCoppaControl: Boolean = false
) : Configuration(apiKey, flushQueueSize, flushIntervalMillis, instanceName, optOut, storageProvider, loggerProvider, minIdLength, callback)
val useAdvertisingIdForDeviceId: Boolean = false,
val useAppSetIdForDeviceId: Boolean = false,
val newDeviceIdPerInstall: Boolean = false,
val trackingOptions: TrackingOptions = TrackingOptions(),
val enableCoppaControl: Boolean = false,
val locationListening: Boolean = true,
val flushEventsOnClose: Boolean = true,
val minTimeBetweenSessionsMillis: Long = MIN_TIME_BETWEEN_SESSIONS_MILLIS,
val trackingSessionEvents: Boolean = true
) : Configuration(apiKey, flushQueueSize, flushIntervalMillis, instanceName, optOut, storageProvider, loggerProvider, minIdLength, callback) {
companion object {
const val MIN_TIME_BETWEEN_SESSIONS_MILLIS: Long = 5 * 60 * 1000
}
}
Loading

0 comments on commit 25b6da3

Please sign in to comment.