Skip to content

Commit

Permalink
feat: add Application Installed and Application Updated events (#412)
Browse files Browse the repository at this point in the history
* feat: add option to save and fetch versionName and build

In RudderPreferenceManager.kt class

* feat: add option to save and fetch versionName and build

In AndroidStorage.kt

* feat: add option to save and fetch versionName and build

In AndroidStorageImpl.kt

* feat: add AppVersion.kt data class

* feat: add AppVersionManager.kt

* feat: add AppInstallUpdateTrackerPlugin.kt

* feat: add AppInstallUpdateTrackerPlugin inside infrastructure plugin list

* refactor: change build to versionCode

* refactor: move business logic outside of AppVersion

* refactor: move AppVersion to model package

* refactor: change versionCode back to build

* chore: remove AppVersionManager.kt

* refactor: move the lifecycle logic to AppInstallUpdateTrackerPlugin.kt

* test: add AppInstallUpdateTrackerPluginTest.kt

* refactor: change versionCode param to build

* chore: simplify logic to save versionName and build

* test: add more test case in AppInstallUpdateTrackerPluginTest.kt
  • Loading branch information
1abhishekpandey authored Apr 12, 2024
1 parent 952e48b commit c4a99b1
Show file tree
Hide file tree
Showing 7 changed files with 480 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.rudderstack.android

import com.rudderstack.android.internal.infrastructure.ActivityBroadcasterPlugin
import com.rudderstack.android.internal.infrastructure.AnonymousIdHeaderPlugin
import com.rudderstack.android.internal.infrastructure.AppInstallUpdateTrackerPlugin
import com.rudderstack.android.internal.infrastructure.LifecycleObserverPlugin
import com.rudderstack.android.internal.infrastructure.ResetImplementationPlugin
import com.rudderstack.android.internal.plugins.ReinstatePlugin
Expand Down Expand Up @@ -136,6 +137,7 @@ fun Analytics.setUserId(userId: String) {
private val infrastructurePlugins
get() = arrayOf(
AnonymousIdHeaderPlugin(),
AppInstallUpdateTrackerPlugin(),
LifecycleObserverPlugin(),
ActivityBroadcasterPlugin(),
ResetImplementationPlugin()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ private const val RUDDER_PERIODIC_WORK_REQUEST_ID_KEY = "rl_periodic_work_reques
private const val RUDDER_SESSION_ID_KEY = "rl_session_id_key"
private const val RUDDER_SESSION_LAST_ACTIVE_TIMESTAMP_KEY =
"rl_last_event_timestamp_key"
private const val RUDDER_APPLICATION_VERSION_KEY = "rl_application_version_key"
private const val RUDDER_APPLICATION_BUILD_KEY = "rl_application_build_key"
internal class RudderPreferenceManager(application: Application,
private val writeKey: String) {

Expand Down Expand Up @@ -175,4 +177,18 @@ internal class RudderPreferenceManager(application: Application,

val optStatus: Boolean
get() = preferences.getBoolean(RUDDER_OPT_STATUS_KEY.key, false)

fun saveVersionName(versionName: String) {
preferences.edit().putString(RUDDER_APPLICATION_VERSION_KEY, versionName).apply()
}

val versionName: String?
get() = preferences.getString(RUDDER_APPLICATION_VERSION_KEY, null)

fun saveBuild(build: Int) {
preferences.edit().putInt(RUDDER_APPLICATION_BUILD_KEY, build).apply()
}

val build: Int
get() = preferences.getInt(RUDDER_APPLICATION_BUILD_KEY, -1)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.rudderstack.android.internal.infrastructure

import android.content.pm.PackageManager
import android.os.Build
import com.rudderstack.android.androidStorage
import com.rudderstack.android.currentConfigurationAndroid
import com.rudderstack.android.storage.AndroidStorage
import com.rudderstack.models.AppVersion
import com.rudderstack.core.Analytics
import com.rudderstack.core.InfrastructurePlugin

private const val PREVIOUS_VERSION = "previous_version"
private const val PREVIOUS_BUILD = "previous_build"
private const val VERSION = "version"
private const val BUILD = "build"

private const val EVENT_NAME_APPLICATION_INSTALLED = "Application Installed"
private const val EVENT_NAME_APPLICATION_UPDATED = "Application Updated"

private const val DEFAULT_BUILD = -1
private const val DEFAULT_VERSION_NAME = ""

class AppInstallUpdateTrackerPlugin : InfrastructurePlugin {

private var analytics: Analytics? = null
private lateinit var appVersion: AppVersion

override fun setup(analytics: Analytics) {
this.analytics = analytics
this.appVersion = getAppVersion(analytics)
storeVersionNameAndBuild(analytics.androidStorage)
if (this.analytics?.currentConfigurationAndroid?.trackLifecycleEvents == true) {
trackApplicationStatus()
}
}

private fun getAppVersion(analytics: Analytics): AppVersion {
val previousBuild: Int? = analytics.androidStorage.build
val previousVersionName: String? = analytics.androidStorage.versionName
var currentBuild: Int? = null
var currentVersionName: String? = null

try {
val packageName = analytics.currentConfigurationAndroid?.application?.packageName
val packageManager: PackageManager? = analytics.currentConfigurationAndroid?.application?.packageManager
val packageInfo = packageName?.let {
packageManager?.getPackageInfo(it, 0)
}

currentVersionName = packageInfo?.versionName
currentBuild = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo?.longVersionCode?.toInt()
} else {
packageInfo?.versionCode
}
} catch (ex: PackageManager.NameNotFoundException) {
analytics.logger.error(log = "Failed to get app version info: ${ex.message}")
}

return AppVersion(
previousBuild = previousBuild ?: DEFAULT_BUILD,
previousVersionName = previousVersionName ?: DEFAULT_VERSION_NAME,
currentBuild = currentBuild ?: DEFAULT_BUILD,
currentVersionName = currentVersionName ?: DEFAULT_VERSION_NAME,
)
}

private fun storeVersionNameAndBuild(analyticsStorage: AndroidStorage) {
analyticsStorage.setVersionName(this.appVersion.currentVersionName)
analyticsStorage.setBuild(this.appVersion.currentBuild)
}

private fun trackApplicationStatus() {
if (this.isApplicationInstalled()) {
sendApplicationInstalledEvent()
} else if (this.isApplicationUpdated()) {
sendApplicationUpdatedEvent()
}
}

private fun isApplicationInstalled(): Boolean {
return this.appVersion.previousBuild == -1
}

private fun isApplicationUpdated(): Boolean {
return this.appVersion.previousBuild != -1 && this.appVersion.previousBuild != this.appVersion.currentBuild
}

private fun sendApplicationInstalledEvent() {
this.analytics?.logger?.debug(log = "Tracking Application Installed event")
val trackProperties = mutableMapOf<String, Any>()
trackProperties[VERSION] = this.appVersion.currentVersionName
trackProperties[BUILD] = this.appVersion.currentBuild

sendEvent(EVENT_NAME_APPLICATION_INSTALLED, trackProperties)
}

private fun sendApplicationUpdatedEvent() {
this.analytics?.logger?.debug(log = "Tracking Application Updated event")
val trackProperties = mutableMapOf<String, Any>()
trackProperties[PREVIOUS_VERSION] = this.appVersion.previousVersionName
trackProperties[PREVIOUS_BUILD] = this.appVersion.previousBuild
trackProperties[VERSION] = this.appVersion.currentVersionName
trackProperties[BUILD] = this.appVersion.currentBuild

sendEvent(EVENT_NAME_APPLICATION_UPDATED, trackProperties)
}

private fun sendEvent(eventName: String, properties: Map<String, Any>) {
analytics?.track {
event(eventName)
trackProperties {
add(properties)
}
}
}

override fun shutdown() {
analytics = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface AndroidStorage : Storage {
val v1Traits: Map<String, Any?>?
val v1ExternalIds: List<Map<String, String>>?
val trackAutoSession: Boolean
val build: Int?
val versionName: String?
/**
* Platform specific implementation of caching context. This can be done locally too.
*
Expand All @@ -55,4 +57,6 @@ interface AndroidStorage : Storage {
fun resetV1OptOut()
fun resetV1Traits()
fun resetV1ExternalIds()
}
fun setBuild(build: Int)
fun setVersionName(versionName: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ class AndroidStorageImpl(
}
override val trackAutoSession: Boolean
get() = preferenceManager?.trackAutoSession?: false
override val build: Int?
get() = preferenceManager?.build
override val versionName: String?
get() = preferenceManager?.versionName

override fun setAnonymousId(anonymousId: String) {
_anonymousId = anonymousId
Expand Down Expand Up @@ -382,6 +386,14 @@ class AndroidStorageImpl(
preferenceManager?.resetV1ExternalIds()
}

override fun setBuild(build: Int) {
preferenceManager?.saveBuild(build)
}

override fun setVersionName(versionName: String) {
preferenceManager?.saveVersionName(versionName)
}

override val libraryName: String
get() = BuildConfig.LIBRARY_PACKAGE_NAME
override val libraryVersion: String
Expand Down Expand Up @@ -413,7 +425,6 @@ class AndroidStorageImpl(
private fun importV1Data() {
val oldDbName = "events.db"
val oldDb = application.getDatabasePath(oldDbName)

}

}
Loading

0 comments on commit c4a99b1

Please sign in to comment.