diff --git a/embrace-android-api/api/embrace-android-api.api b/embrace-android-api/api/embrace-android-api.api index dd0482ec89..027f8aa9a8 100644 --- a/embrace-android-api/api/embrace-android-api.api +++ b/embrace-android-api/api/embrace-android-api.api @@ -118,27 +118,6 @@ public abstract interface class io/embrace/android/embracesdk/internal/api/UserA public abstract fun setUsername (Ljava/lang/String;)V } -public abstract interface class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents { - public abstract fun create (ILjava/lang/String;J)V - public abstract fun createEnd (IJ)V - public abstract fun hibernate (ILjava/lang/String;J)V - public abstract fun render (ILjava/lang/String;J)V - public abstract fun renderEnd (IJ)V - public abstract fun resetTrace (ILjava/lang/String;J)V - public abstract fun resume (ILjava/lang/String;J)V - public abstract fun resumeEnd (IJ)V - public abstract fun start (ILjava/lang/String;J)V - public abstract fun startEnd (IJ)V -} - -public final class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType : java/lang/Enum { - public static final field COLD Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public static final field HOT Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public final fun getTypeName ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public static fun values ()[Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; -} - public class io/embrace/android/embracesdk/internal/network/http/EmbraceHttpPathOverride { protected static final field PATH_OVERRIDE Ljava/lang/String; public fun ()V diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt similarity index 59% rename from embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt rename to embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt index c7ba1031ff..595530012a 100644 --- a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt @@ -3,60 +3,56 @@ package io.embrace.android.embracesdk.internal.capture.activity /** * The relevant stages in the lifecycle of Activities pertaining to observing the performance of their loading */ -public interface OpenEvents { +interface OpenEvents { /** * When a previously in-progress Activity Open trace should be abandoned, and that the component managing * the trace recording should prepare itself to start tracing the opening of a new Activity instance. */ - public fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) + fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) /** * When the app is no longer in a state where it is trying to open up a new Activity */ - public fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) + fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity is entering the CREATE stage of its lifecycle. */ - public fun create(instanceId: Int, activityName: String, timestampMs: Long) + fun create(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the CREATE stage of its lifecycle. */ - public fun createEnd(instanceId: Int, timestampMs: Long) + fun createEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity is entering the START stage of its lifecycle. */ - public fun start(instanceId: Int, activityName: String, timestampMs: Long) + fun start(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the START stage of its lifecycle. */ - public fun startEnd(instanceId: Int, timestampMs: Long) + fun startEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity is entering the RESUME stage of its lifecycle. */ - public fun resume(instanceId: Int, activityName: String, timestampMs: Long) + fun resume(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the RESUME stage of its lifecycle. */ - public fun resumeEnd(instanceId: Int, timestampMs: Long) + fun resumeEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity's first UI frame starts to be rendered. */ - public fun render(instanceId: Int, activityName: String, timestampMs: Long) + fun render(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity's first UI frame has been displayed. */ - public fun renderEnd(instanceId: Int, timestampMs: Long) - - public enum class OpenType(public val typeName: String) { - COLD("cold"), HOT("hot") - } + fun renderEnd(instanceId: Int, timestampMs: Long) } diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt index bdc80949c2..6f62d64af7 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt @@ -11,36 +11,49 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicReference /** - * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen. + * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen after + * app startup has completed. This creates traces for both types of Activity opening specified in [OpenType]. + * * Depending on the version of Android and the state of the app, the start, end, and intermediate stages of the workflow will use - * timestamps from different events, which affects to preciseness of the measurement. + * timestamps from different events, which affects the precision of the measurement. * - * Trace Start: + * The start for [OpenType.COLD]: * * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPostPaused] is fired, denoting that the previous activity has completed * its [ActivityLifecycleCallbacks.onActivityPaused] callbacks and a new Activity is ready to be created. + * * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityPaused] is fired, denoting that the previous activity is in the * process of exiting. This will possibly result in some cleanup work of exiting the previous activity being included in the duration * of the next trace that is logged. * - * Trace End: + * The start for [OpenType.HOT] + * + * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPreStarted] is fired, denoting that an existing Activity instance is ready + * to be started + * + * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityStarted] is fired, denoting that an existing activity is in the + * process of starting. This will possibly result in some of the work to start the activity already having happened depending on the + * other callbacks that have been registered. + * + * The end for both [OpenType.COLD] and [OpenType.HOT]: * * - Android 10+, when the Activity's first UI frame finishes rendering and is delivered to the screen - * - Android 9 and lower.... TODO + * + * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityResumed] is fired. */ -public class OpenTraceEmitter( +class OpenTraceEmitter( private val spanService: SpanService, private val versionChecker: VersionChecker, ) : OpenEvents { private val activeTraces: MutableMap = ConcurrentHashMap() - private val traceIds: MutableMap = ConcurrentHashMap() private val traceZygoteHolder: AtomicReference = AtomicReference(INITIAL) + private var currentTracedInstanceId: Int? = null override fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) { - traceIds[activityName]?.let { existingTraceId -> - if (instanceId != existingTraceId) { - endTrace(instanceId = existingTraceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) + currentTracedInstanceId?.let { currentlyTracedInstanceId -> + if (instanceId != currentlyTracedInstanceId) { + endTrace(instanceId = currentlyTracedInstanceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) } } traceZygoteHolder.set( @@ -60,7 +73,7 @@ public class OpenTraceEmitter( override fun create(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenEvents.OpenType.COLD, + openType = OpenType.COLD, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -82,7 +95,7 @@ public class OpenTraceEmitter( override fun start(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenEvents.OpenType.HOT, + openType = OpenType.HOT, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -147,7 +160,7 @@ public class OpenTraceEmitter( } private fun startTrace( - openType: OpenEvents.OpenType, + openType: OpenType, instanceId: Int, activityName: String, timestampMs: Long @@ -215,16 +228,16 @@ public class OpenTraceEmitter( private fun traceName( activityName: String, - openType: OpenEvents.OpenType + openType: OpenType ): String = "$activityName-${openType.typeName}-open" - public enum class LifecycleEvent(private val typeName: String) { + enum class LifecycleEvent(private val typeName: String) { CREATE("create"), START("start"), RESUME("resume"), RENDER("render"); - public fun spanName(activityName: String): String = "$activityName-$typeName" + fun spanName(activityName: String): String = "$activityName-$typeName" } private data class ActivityOpenTrace( diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt new file mode 100644 index 0000000000..135cf95b5a --- /dev/null +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt @@ -0,0 +1,16 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +/** + * The type of activity opening being traced + */ +enum class OpenType(val typeName: String) { + /** + * Activity opening where the instance has to be created + */ + COLD("cold"), + + /** + * Activity opening where the instance has already been created and just needs to be started and resumed (e.g. app foregrounding) + */ + HOT("hot") +} diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt index 60a5256daa..b76a590b29 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt @@ -12,6 +12,7 @@ import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker import io.opentelemetry.api.trace.SpanId import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -43,8 +44,45 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from another activity in U`() { verifyOpen( - fromBackground = false, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from the same activity in U`() { + verifyOpen( + lastActivityName = ACTIVITY_NAME, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Ignore("Not working yet") + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from an interrupted opening of another activity in U`() { + verifyOpen( + previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Ignore("Not working yet") + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from an interrupted opening of the same activity in U`() { + verifyOpen( + lastActivityName = ACTIVITY_NAME, + previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, + openType = OpenType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -54,8 +92,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from background in U`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -65,8 +103,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify hot open trace in from background in U`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.HOT, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.HOT, firePreAndPost = true, hasRenderEvent = true, ) @@ -76,8 +114,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace in from another activity L`() { verifyOpen( - fromBackground = false, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -87,8 +125,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from background in L`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -98,27 +136,35 @@ internal class OpenTraceEmitterTest { @Test fun `verify hot open trace in L from background`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.HOT, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.HOT, firePreAndPost = false, hasRenderEvent = false, ) } private fun verifyOpen( - fromBackground: Boolean, - openType: OpenEvents.OpenType, + activityName: String = ACTIVITY_NAME, + instanceId: Int = NEW_INSTANCE_ID, + lastActivityName: String = LAST_ACTIVITY_NAME, + lastInstanceId: Int = LAST_ACTIVITY_INSTANCE_ID, + previousState: PreviousState, + openType: OpenType, firePreAndPost: Boolean, hasRenderEvent: Boolean, ) { openActivity( - fromBackground = fromBackground, + activityName = activityName, + instanceId = instanceId, + lastActivityName = lastActivityName, + lastInstanceId = lastInstanceId, + previousState = previousState, openType = openType, firePreAndPost = firePreAndPost, hasRenderEvent = hasRenderEvent ).let { timestamps -> val spanMap = spanSink.completedSpans().associateBy { it.name } - val trace = checkNotNull(spanMap["emb-${ACTIVITY_NAME}-${openType.typeName}-open"]) + val trace = checkNotNull(spanMap["emb-$activityName-${openType.typeName}-open"]) assertEmbraceSpanData( span = trace.toNewPayload(), @@ -129,22 +175,22 @@ internal class OpenTraceEmitterTest { ) val events = timestamps.third - if (openType == OpenEvents.OpenType.COLD) { + if (openType == OpenType.COLD) { checkNotNull(events[OpenTraceEmitter.LifecycleEvent.CREATE]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-create"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-create"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId ) } } else { - assertNull(spanMap["emb-$ACTIVITY_NAME-create"]) + assertNull(spanMap["emb-$activityName-create"]) } checkNotNull(events[OpenTraceEmitter.LifecycleEvent.START]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-start"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-start"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId @@ -154,7 +200,7 @@ internal class OpenTraceEmitterTest { if (hasRenderEvent) { checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RESUME]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-resume"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-resume"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId @@ -162,60 +208,99 @@ internal class OpenTraceEmitterTest { } checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RENDER]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-render"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-render"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId ) } } else { - assertNull(spanMap["emb-$ACTIVITY_NAME-resume"]) - assertNull(spanMap["emb-$ACTIVITY_NAME-render"]) + assertNull(spanMap["emb-$activityName-resume"]) + assertNull(spanMap["emb-$activityName-render"]) } } } @Suppress("CyclomaticComplexMethod", "ComplexMethod") private fun openActivity( - fromBackground: Boolean, - openType: OpenEvents.OpenType, + activityName: String, + instanceId: Int, + lastActivityName: String, + lastInstanceId: Int, + previousState: PreviousState, + openType: OpenType, firePreAndPost: Boolean, - hasRenderEvent: Boolean + hasRenderEvent: Boolean, ): Triple> { val events = mutableMapOf() val lastActivityExitMs = clock.now() - traceEmitter.resetTrace(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, lastActivityExitMs) clock.tick(100L) - if (fromBackground) { - traceEmitter.hibernate(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, clock.now()) + when (previousState) { + PreviousState.FROM_ACTIVITY -> { + traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) + } + + PreviousState.FROM_BACKGROUND -> { + traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) + traceEmitter.hibernate(lastInstanceId, lastActivityName, clock.now()) + } + + PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN -> { + activityCreate( + activityName = lastActivityName, + instanceId = lastInstanceId, + firePreAndPost = firePreAndPost + ) + activityStart( + activityName = lastActivityName, + instanceId = lastInstanceId, + firePreAndPost = firePreAndPost + ) + } } - val createEvents = if (openType == OpenEvents.OpenType.COLD) { - activityCreate(firePreAndPost) + val createEvents = if (openType == OpenType.COLD) { + activityCreate( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ) } else { null }?.apply { events[OpenTraceEmitter.LifecycleEvent.CREATE] = this } - val startEvents = activityStart(firePreAndPost).apply { + val startEvents = activityStart( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ).apply { events[OpenTraceEmitter.LifecycleEvent.START] = this } - val resumeEvents = activityResume(firePreAndPost).apply { + val resumeEvents = activityResume( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ).apply { events[OpenTraceEmitter.LifecycleEvent.RESUME] = this } val renderEvents = if (hasRenderEvent) { - activityRender(firePreAndPost) + activityRender( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ) } else { null }?.apply { events[OpenTraceEmitter.LifecycleEvent.RENDER] = this } - val traceStartMs = if (!fromBackground) { + val traceStartMs = if (previousState != PreviousState.FROM_BACKGROUND) { lastActivityExitMs } else { createEvents?.run { @@ -238,32 +323,56 @@ internal class OpenTraceEmitterTest { return Triple(traceStartMs, traceEndMs, events) } - private fun activityCreate(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityCreate( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::create, endCallback = traceEmitter::createEnd, firePreAndPost = firePreAndPost, ) } - private fun activityStart(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityStart( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::start, endCallback = traceEmitter::startEnd, firePreAndPost = firePreAndPost, ) } - private fun activityResume(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityResume( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::resume, endCallback = traceEmitter::resumeEnd, firePreAndPost = firePreAndPost, ) } - private fun activityRender(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityRender( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::render, endCallback = traceEmitter::renderEnd, firePreAndPost = firePreAndPost, @@ -271,6 +380,8 @@ internal class OpenTraceEmitterTest { } private fun runLifecycleEvent( + activityName: String, + instanceId: Int, startCallback: (instanceId: Int, activityName: String, startMs: Long) -> Unit, endCallback: (instanceId: Int, startMs: Long) -> Unit, firePreAndPost: Boolean = true @@ -281,12 +392,12 @@ internal class OpenTraceEmitterTest { clock.tick() } events.eventStart = clock.now() - startCallback(INSTANCE_1, ACTIVITY_NAME, events.startMs()) + startCallback(instanceId, activityName, events.startMs()) events.eventEnd = clock.tick(100L) if (firePreAndPost) { events.post = clock.tick() } - endCallback(INSTANCE_1, events.endMs()) + endCallback(instanceId, events.endMs()) return events } @@ -301,10 +412,16 @@ internal class OpenTraceEmitterTest { private fun LifecycleEvents.endMs(): Long = max(post, eventEnd) + private enum class PreviousState { + FROM_ACTIVITY, + FROM_BACKGROUND, + FROM_INTERRUPTED_ACTIVITY_OPEN + } + companion object { - const val INSTANCE_1 = 1 + const val NEW_INSTANCE_ID = 1 const val ACTIVITY_NAME = "com.my.CoolActivity" - const val LAST_ACTIVITY = "com.my.Activity" - const val LAST_ACTIVITY_INSTANCE = 99 + const val LAST_ACTIVITY_NAME = "com.my.Activity" + const val LAST_ACTIVITY_INSTANCE_ID = 99 } }