-
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.
Create OS-specific implementations of the ActivityLoadEventEmitter
- Loading branch information
1 parent
08c260c
commit d890ed6
Showing
7 changed files
with
141 additions
and
111 deletions.
There are no files selected for viewing
138 changes: 84 additions & 54 deletions
138
...ture/activity/ActivityLoadEventEmitter.kt → ...dk/internal/capture/activity/UiLoadExt.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 |
---|---|---|
@@ -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) | ||
} | ||
} | ||
|
||
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) | ||
} |
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
Oops, something went wrong.