-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e31760d
commit ddf4a92
Showing
9 changed files
with
335 additions
and
19 deletions.
There are no files selected for viewing
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
54 changes: 54 additions & 0 deletions
54
...dk/src/main/java/io/embrace/android/embracesdk/capture/startup/AppStartupDataCollector.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,54 @@ | ||
package io.embrace.android.embracesdk.capture.startup | ||
|
||
/** | ||
* Collects relevant information during app startup to be used to produce telemetry about the startup workflow. | ||
* | ||
* Due to differences in behaviour between platform versions and various startup scenarios, you cannot assume that these methods | ||
* will be invoked in any order or at all. Implementations need to take into account that fact when using the underlying data. | ||
*/ | ||
internal interface AppStartupDataCollector { | ||
/** | ||
* Set the time when the application object initialization was started | ||
*/ | ||
fun applicationInitStart(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time when the application object initialization has finished | ||
*/ | ||
fun applicationInitEnd(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time just prior to the creation of the Activity whose rendering will denote the end of the startup workflow | ||
*/ | ||
fun startupActivityPreCreated(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time for the start of the initialization of the Activity whose rendering will denote the end of the startup workflow | ||
*/ | ||
fun startupActivityInitStart(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time just after the creation of the Activity whose rendering will denote the end of the startup workflow | ||
*/ | ||
fun startupActivityPostCreated(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time for the end of the initialization of the Activity whose rendering will denote the end of the startup workflow | ||
*/ | ||
fun startupActivityInitEnd(timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time for when the startup Activity begins to render as well as its name | ||
*/ | ||
fun startupActivityResumed(activityName: String, timestampMs: Long? = null) | ||
|
||
/** | ||
* Set the time for when the startup Activity has finished rendering its first frame as well as its name | ||
*/ | ||
fun firstFrameRendered(activityName: String, timestampMs: Long? = null) | ||
|
||
/** | ||
* Set an arbitrary time interval during startup that is of note | ||
*/ | ||
fun addTrackedInterval(name: String, startTimeMs: Long, endTimeMs: Long) | ||
} |
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
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
189 changes: 189 additions & 0 deletions
189
...oid-sdk/src/test/java/io/embrace/android/embracesdk/capture/startup/StartupTrackerTest.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,189 @@ | ||
package io.embrace.android.embracesdk.capture.startup | ||
|
||
import android.app.Activity | ||
import android.app.Application | ||
import android.os.Build | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import io.embrace.android.embracesdk.fakes.FakeActivity | ||
import io.embrace.android.embracesdk.fakes.FakeAppStartupDataCollector | ||
import io.embrace.android.embracesdk.fakes.FakeClock | ||
import io.embrace.android.embracesdk.fakes.FakeSplashScreenActivity | ||
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker | ||
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger | ||
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.RuntimeEnvironment | ||
import org.robolectric.android.controller.ActivityController | ||
import org.robolectric.annotation.Config | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
internal class StartupTrackerTest { | ||
private lateinit var application: Application | ||
private lateinit var clock: FakeClock | ||
private lateinit var dataCollector: FakeAppStartupDataCollector | ||
private lateinit var logger: InternalEmbraceLogger | ||
private lateinit var startupTracker: StartupTracker | ||
private lateinit var defaultActivityController: ActivityController<Activity> | ||
|
||
@Before | ||
fun setUp() { | ||
application = RuntimeEnvironment.getApplication() | ||
clock = FakeClock() | ||
logger = InternalEmbraceLogger() | ||
dataCollector = FakeAppStartupDataCollector(clock = clock) | ||
startupTracker = StartupTracker( | ||
appStartupDataCollector = dataCollector, | ||
logger = logger, | ||
versionChecker = BuildVersionChecker | ||
) | ||
application.registerActivityLifecycleCallbacks(startupTracker) | ||
defaultActivityController = Robolectric.buildActivity(Activity::class.java) | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
@Test | ||
fun `cold start in U`() { | ||
with(launchActivity()) { | ||
assertEquals("android.app.Activity", dataCollector.startupActivityName) | ||
verifyTiming( | ||
preCreateTime = createTime, | ||
createTime = createTime, | ||
postCreateTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.Q]) | ||
@Test | ||
fun `cold start in Q`() { | ||
with(launchActivity()) { | ||
verifyTiming( | ||
preCreateTime = createTime, | ||
createTime = createTime, | ||
postCreateTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.P]) | ||
@Test | ||
fun `cold start in P`() { | ||
with(launchActivity()) { | ||
verifyTiming( | ||
createTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) | ||
@Test | ||
fun `cold start in L`() { | ||
with(launchActivity()) { | ||
verifyTiming( | ||
createTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
@Test | ||
fun `cold start with activity instance recreated`() { | ||
defaultActivityController.create() | ||
clock.tick() | ||
val recreateTime = clock.now() | ||
defaultActivityController.recreate() | ||
clock.tick() | ||
val startTime = clock.now() | ||
defaultActivityController.start() | ||
clock.tick() | ||
val resumeTime = clock.now() | ||
defaultActivityController.resume() | ||
clock.tick() | ||
|
||
verifyTiming( | ||
preCreateTime = recreateTime, | ||
createTime = recreateTime, | ||
postCreateTime = recreateTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
@Test | ||
fun `cold start with different activities being created and foregrounded first`() { | ||
defaultActivityController.create() | ||
clock.tick() | ||
with(launchActivity(Robolectric.buildActivity(FakeActivity::class.java))) { | ||
assertEquals("io.embrace.android.embracesdk.fakes.FakeActivity", dataCollector.startupActivityName) | ||
verifyTiming( | ||
preCreateTime = createTime, | ||
createTime = createTime, | ||
postCreateTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
@Test | ||
fun `cold start initial activity not tracked will use the second for timing`() { | ||
launchActivity(Robolectric.buildActivity(FakeSplashScreenActivity::class.java)) | ||
clock.tick() | ||
with(launchActivity()) { | ||
assertEquals("android.app.Activity", dataCollector.startupActivityName) | ||
verifyTiming( | ||
preCreateTime = createTime, | ||
createTime = createTime, | ||
postCreateTime = createTime, | ||
startTime = startTime, | ||
resumeTime = resumeTime | ||
) | ||
} | ||
} | ||
|
||
private fun launchActivity(controller: ActivityController<*> = defaultActivityController): ActivityTiming { | ||
val createTime = clock.now() | ||
controller.create() | ||
clock.tick() | ||
val startTime = clock.now() | ||
controller.start() | ||
clock.tick() | ||
val resumeTime = clock.now() | ||
controller.resume() | ||
clock.tick() | ||
return ActivityTiming(createTime, startTime, resumeTime) | ||
} | ||
|
||
private fun verifyTiming( | ||
preCreateTime: Long? = null, | ||
createTime: Long, | ||
postCreateTime: Long? = null, | ||
startTime: Long, | ||
resumeTime: Long | ||
) { | ||
assertEquals(preCreateTime, dataCollector.startupActivityPreCreatedMs) | ||
assertEquals(createTime, dataCollector.startupActivityInitStartMs) | ||
assertEquals(postCreateTime, dataCollector.startupActivityPostCreatedMs) | ||
assertEquals(startTime, dataCollector.startupActivityInitEndMs) | ||
assertEquals(resumeTime, dataCollector.startupActivityResumedMs) | ||
} | ||
|
||
private data class ActivityTiming( | ||
val createTime: Long, | ||
val startTime: Long, | ||
val resumeTime: Long | ||
) | ||
} |
Oops, something went wrong.