-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Component to map lifecycle events to the activity open events
- Loading branch information
1 parent
17ed531
commit 9806c9a
Showing
8 changed files
with
586 additions
and
3 deletions.
There are no files selected for viewing
151 changes: 151 additions & 0 deletions
151
...c/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package io.embrace.android.embracesdk.internal.capture.activity | ||
|
||
import android.app.Activity | ||
import android.app.Application.ActivityLifecycleCallbacks | ||
import android.os.Build | ||
import android.os.Bundle | ||
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 [OpenEvents] depending on the current state of the app and capabilities of the OS. | ||
* | ||
* 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 [OpenTraceEmitter] for details. | ||
*/ | ||
public class OpenEventEmitter( | ||
private val openEvents: OpenEvents, | ||
private val clock: Clock, | ||
private val versionChecker: VersionChecker, | ||
) : ActivityLifecycleListener { | ||
|
||
override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) { | ||
create(activity) | ||
} | ||
|
||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) { | ||
if (!versionChecker.firePrePostEvents()) { | ||
create(activity) | ||
} | ||
} | ||
|
||
override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) { | ||
createEnd(activity) | ||
} | ||
|
||
override fun onActivityPreStarted(activity: Activity) { | ||
start(activity) | ||
} | ||
|
||
override fun onActivityStarted(activity: Activity) { | ||
if (!versionChecker.firePrePostEvents()) { | ||
createEnd(activity) | ||
start(activity) | ||
} | ||
} | ||
|
||
override fun onActivityPostStarted(activity: Activity) { | ||
startEnd(activity) | ||
} | ||
|
||
override fun onActivityPreResumed(activity: Activity) { | ||
resume(activity) | ||
} | ||
|
||
override fun onActivityResumed(activity: Activity) { | ||
if (!versionChecker.firePrePostEvents()) { | ||
startEnd(activity) | ||
resume(activity) | ||
} | ||
} | ||
|
||
override fun onActivityPostResumed(activity: Activity) { | ||
resumeEnd(activity) | ||
} | ||
|
||
override fun onActivityPaused(activity: Activity) { | ||
if (!versionChecker.firePrePostEvents()) { | ||
resetTrace(activity) | ||
} | ||
} | ||
|
||
override fun onActivityPostPaused(activity: Activity) { | ||
resetTrace(activity) | ||
} | ||
|
||
override fun onActivityStopped(activity: Activity) { | ||
hibernate(activity) | ||
} | ||
|
||
private fun resetTrace(activity: Activity) { | ||
openEvents.resetTrace( | ||
instanceId = traceInstanceId(activity), | ||
activityName = activity.localClassName, | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun hibernate(activity: Activity) { | ||
openEvents.hibernate( | ||
instanceId = traceInstanceId(activity), | ||
activityName = activity.localClassName, | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun create(activity: Activity) { | ||
openEvents.create( | ||
instanceId = traceInstanceId(activity), | ||
activityName = activity.localClassName, | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun createEnd(activity: Activity) { | ||
openEvents.createEnd( | ||
instanceId = traceInstanceId(activity), | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun start(activity: Activity) { | ||
openEvents.start( | ||
instanceId = traceInstanceId(activity), | ||
activityName = activity.localClassName, | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun startEnd(activity: Activity) { | ||
openEvents.startEnd( | ||
instanceId = traceInstanceId(activity), | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun resume(activity: Activity) { | ||
openEvents.resume( | ||
instanceId = traceInstanceId(activity), | ||
activityName = activity.localClassName, | ||
timestampMs = nowMs() | ||
) | ||
} | ||
|
||
private fun resumeEnd(activity: Activity) { | ||
openEvents.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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
...test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitterTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package io.embrace.android.embracesdk.internal.capture.activity | ||
|
||
import android.app.Activity | ||
import android.os.Build | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import io.embrace.android.embracesdk.fakes.FakeClock | ||
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule | ||
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.robolectric.Robolectric | ||
import org.robolectric.android.controller.ActivityController | ||
import org.robolectric.annotation.Config | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
internal class OpenEventEmitterTest { | ||
private lateinit var clock: FakeClock | ||
private lateinit var openEvents: FakeOpenEvents | ||
private lateinit var eventEmitter: OpenEventEmitter | ||
private lateinit var activityController: ActivityController<*> | ||
|
||
@Before | ||
fun setUp() { | ||
clock = FakeClock() | ||
val initModule = FakeInitModule(clock = clock) | ||
clock.tick(100L) | ||
openEvents = FakeOpenEvents() | ||
eventEmitter = OpenEventEmitter( | ||
openEvents = openEvents, | ||
clock = initModule.openTelemetryModule.openTelemetryClock, | ||
versionChecker = BuildVersionChecker, | ||
) | ||
activityController = Robolectric.buildActivity(Activity::class.java) | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
@Test | ||
fun `check open event stages in U`() { | ||
with(activityController) { | ||
clock.tick() | ||
create() | ||
clock.tick() | ||
start() | ||
clock.tick() | ||
resume() | ||
clock.tick() | ||
pause() | ||
clock.tick() | ||
stop() | ||
clock.tick() | ||
} | ||
assertEquals(8, openEvents.events.count()) | ||
} | ||
|
||
class FakeOpenEvents : OpenEvents { | ||
val events = mutableListOf<EventData>() | ||
|
||
override fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "resetTrace", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "hibernate", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun create(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "create", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun createEnd(instanceId: Int, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "createEnd", | ||
instanceId = instanceId, | ||
activityName = null, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun start(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "start", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun startEnd(instanceId: Int, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "startEnd", | ||
instanceId = instanceId, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun resume(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "resume", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun resumeEnd(instanceId: Int, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "resumeEnd", | ||
instanceId = instanceId, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun render(instanceId: Int, activityName: String, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "render", | ||
instanceId = instanceId, | ||
activityName = activityName, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
override fun renderEnd(instanceId: Int, timestampMs: Long) { | ||
events.add( | ||
EventData( | ||
stage = "renderEnd", | ||
instanceId = instanceId, | ||
timestampMs = timestampMs | ||
) | ||
) | ||
} | ||
|
||
data class EventData( | ||
val stage: String, | ||
val instanceId: Int, | ||
val activityName: String? = null, | ||
val timestampMs: Long, | ||
) | ||
} | ||
} |
Oops, something went wrong.