Skip to content

Commit

Permalink
Create OS-specific implementations of the ActivityLoadEventEmitter
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Nov 15, 2024
1 parent 61b6940 commit 5b674be
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -1,165 +1,195 @@
package io.embrace.android.embracesdk.internal.capture.activity

import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Bundle
import androidx.annotation.RequiresApi
import io.embrace.android.embracesdk.annotation.ObservedActivity
import io.embrace.android.embracesdk.internal.clock.nanosToMillis
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
import io.embrace.android.embracesdk.internal.utils.VersionChecker
import io.opentelemetry.sdk.common.Clock

/**
* Maps [ActivityLifecycleCallbacks] events to [UiLoadEventListener] depending on version of the OS and whether or
* not the given [Activity]'s load should be traced.
* Creates [ActivityLifecycleListener] that maps Activity lifecycle events to the given [UiLoadEventListener].
* It will create an implementation that uses the most suitable API given the OS version.
*
* The purpose of this is to leverage Activity lifecycle events to provide data for the underlying workflow to bring a new Activity on
* screen. Due to the varying capabilities of the APIs available on the different versions of Android, the precise triggering events for
* the start and intermediate steps may differ.
*
* See [UiLoadTraceEmitter] for details about how these events are turned into traces.
* For details of how these events are used to create UI Load traces, see [UiLoadTraceEmitter] for details.
*/
class ActivityLoadEventEmitter(
private val uiLoadEventListener: UiLoadEventListener,
private val clock: Clock,
private val versionChecker: VersionChecker,
fun createActivityLoadEventEmitter(
uiLoadEventListener: UiLoadEventListener,
clock: Clock,
versionChecker: VersionChecker,
): ActivityLifecycleListener {
val uiLoadEventEmitter = UiLoadEventEmitter(
uiLoadEventListener = uiLoadEventListener,
clock = clock,
)
return if (versionChecker.isAtLeast(VERSION_CODES.Q)) {
ActivityLoadEventEmitter(uiLoadEventEmitter)
} else {
LegacyActivityLoadEventEmitter(uiLoadEventEmitter)
}
}

private fun Activity.observeOpening() = javaClass.isAnnotationPresent(ObservedActivity::class.java)

/**
* Implementation that works with Android 10+ APIs
*/
@RequiresApi(VERSION_CODES.Q)
private class ActivityLoadEventEmitter(
private val uiLoadEventEmitter: UiLoadEventEmitter
) : ActivityLifecycleListener {

override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity.observeOpening()) {
create(activity)
}
}

override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
if (activity.observeOpening() && !versionChecker.firePrePostEvents()) {
create(activity)
uiLoadEventEmitter.create(activity)
}
}

override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity.observeOpening()) {
createEnd(activity)
uiLoadEventEmitter.createEnd(activity)
}
}

override fun onActivityPreStarted(activity: Activity) {
if (activity.observeOpening()) {
start(activity)
uiLoadEventEmitter.start(activity)
}
}

override fun onActivityStarted(activity: Activity) {
if (activity.observeOpening() && !versionChecker.firePrePostEvents()) {
createEnd(activity)
start(activity)
override fun onActivityPostStarted(activity: Activity) {
if (activity.observeOpening()) {
uiLoadEventEmitter.startEnd(activity)
}
}

override fun onActivityPostStarted(activity: Activity) {
override fun onActivityPreResumed(activity: Activity) {
if (activity.observeOpening()) {
startEnd(activity)
uiLoadEventEmitter.resume(activity)
}
}

override fun onActivityPreResumed(activity: Activity) {
override fun onActivityPostResumed(activity: Activity) {
if (activity.observeOpening()) {
resume(activity)
uiLoadEventEmitter.resumeEnd(activity)
}
}

override fun onActivityResumed(activity: Activity) {
if (activity.observeOpening() && !versionChecker.firePrePostEvents()) {
startEnd(activity)
resume(activity)
override fun onActivityPrePaused(activity: Activity) {
uiLoadEventEmitter.abandonTrace(activity)
}

override fun onActivityStopped(activity: Activity) {
uiLoadEventEmitter.reset(activity)
}
}

/**
* Version of [ActivityLoadEventEmitter] that works with all Android version and used for Android 9 or lower
*/
private class LegacyActivityLoadEventEmitter(
private val uiLoadEventEmitter: UiLoadEventEmitter
) : ActivityLifecycleListener {

override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
if (activity.observeOpening()) {
uiLoadEventEmitter.create(activity)
}
}

override fun onActivityPostResumed(activity: Activity) {
override fun onActivityStarted(activity: Activity) {
if (activity.observeOpening()) {
resumeEnd(activity)
uiLoadEventEmitter.createEnd(activity)
uiLoadEventEmitter.start(activity)
}
}

override fun onActivityPrePaused(activity: Activity) {
abandonTrace(activity)
override fun onActivityResumed(activity: Activity) {
if (activity.observeOpening()) {
uiLoadEventEmitter.startEnd(activity)
uiLoadEventEmitter.resume(activity)
}
}

override fun onActivityPaused(activity: Activity) {
if (!versionChecker.firePrePostEvents()) {
abandonTrace(activity)
}
uiLoadEventEmitter.abandonTrace(activity)
}

override fun onActivityStopped(activity: Activity) {
reset(activity)
uiLoadEventEmitter.reset(activity)
}
}

private fun abandonTrace(activity: Activity) {
/**
* Maps an Activity instance's UI Load events the app-wide UI [UiLoadEventListener]
*/
private class UiLoadEventEmitter(
private val uiLoadEventListener: UiLoadEventListener,
private val clock: Clock,
) {
fun abandonTrace(activity: Activity) {
uiLoadEventListener.abandon(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun reset(activity: Activity) {
fun reset(activity: Activity) {
uiLoadEventListener.reset(
lastInstanceId = traceInstanceId(activity),
)
}

private fun create(activity: Activity) {
fun create(activity: Activity) {
uiLoadEventListener.create(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun createEnd(activity: Activity) {
fun createEnd(activity: Activity) {
uiLoadEventListener.createEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun start(activity: Activity) {
fun start(activity: Activity) {
uiLoadEventListener.start(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun startEnd(activity: Activity) {
fun startEnd(activity: Activity) {
uiLoadEventListener.startEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun resume(activity: Activity) {
fun resume(activity: Activity) {
uiLoadEventListener.resume(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun resumeEnd(activity: Activity) {
fun resumeEnd(activity: Activity) {
uiLoadEventListener.resumeEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun VersionChecker.firePrePostEvents(): Boolean = isAtLeast(Build.VERSION_CODES.Q)

private fun traceInstanceId(activity: Activity): Int = activity.hashCode()

private fun nowMs(): Long = clock.now().nanosToMillis()

private fun Activity.observeOpening() = javaClass.isAnnotationPresent(ObservedActivity::class.java)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.embrace.android.embracesdk.internal.injection

import io.embrace.android.embracesdk.internal.capture.activity.ActivityLoadEventEmitter
import io.embrace.android.embracesdk.internal.capture.activity.UiLoadEventListener
import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker
import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector
import io.embrace.android.embracesdk.internal.capture.startup.StartupService
import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker
import io.embrace.android.embracesdk.internal.capture.webview.WebViewService
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener

/**
* This modules provides services that capture data from within an application. It could be argued
Expand Down Expand Up @@ -43,5 +43,5 @@ interface DataCaptureServiceModule {

val uiLoadTraceEmitter: UiLoadEventListener

val activityLoadEventEmitter: ActivityLoadEventEmitter?
val activityLoadEventEmitter: ActivityLifecycleListener?
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.embrace.android.embracesdk.internal.injection

import io.embrace.android.embracesdk.internal.Systrace
import io.embrace.android.embracesdk.internal.capture.activity.ActivityLoadEventEmitter
import io.embrace.android.embracesdk.internal.capture.activity.UiLoadEventListener
import io.embrace.android.embracesdk.internal.capture.activity.UiLoadTraceEmitter
import io.embrace.android.embracesdk.internal.capture.activity.createActivityLoadEventEmitter
import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker
import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector
Expand All @@ -14,6 +14,7 @@ import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker
import io.embrace.android.embracesdk.internal.capture.webview.EmbraceWebViewService
import io.embrace.android.embracesdk.internal.capture.webview.WebViewService
import io.embrace.android.embracesdk.internal.config.ConfigService
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
import io.embrace.android.embracesdk.internal.utils.VersionChecker
import io.embrace.android.embracesdk.internal.worker.Worker
Expand Down Expand Up @@ -78,12 +79,12 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
)
}

override val activityLoadEventEmitter: ActivityLoadEventEmitter? by singleton {
override val activityLoadEventEmitter: ActivityLifecycleListener? by singleton {
if (configService.autoDataCaptureBehavior.isUiLoadPerfCaptureEnabled()) {
ActivityLoadEventEmitter(
createActivityLoadEventEmitter(
uiLoadEventListener = uiLoadTraceEmitter,
clock = openTelemetryModule.openTelemetryClock,
versionChecker = versionChecker,
versionChecker = versionChecker
)
} else {
null
Expand Down
Loading

0 comments on commit 5b674be

Please sign in to comment.