diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 65170af4..742620eb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,14 @@ android { } } +androidComponents { + finalizeDsl { extension -> + extension.sourceSets.getByName("benchmarkRelease").manifest.srcFile( + "src/benchmarkRelease/AndroidManifest.xml", + ) + } +} + dependencies { implementation(projects.core.common) implementation(projects.core.analytics) diff --git a/app/src/benchmarkRelease/AndroidManifest.xml b/app/src/benchmarkRelease/AndroidManifest.xml new file mode 100644 index 00000000..654c3eac --- /dev/null +++ b/app/src/benchmarkRelease/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/BenchmarkHostActivity.kt b/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/BenchmarkHostActivity.kt new file mode 100644 index 00000000..1aa0d712 --- /dev/null +++ b/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/BenchmarkHostActivity.kt @@ -0,0 +1,61 @@ +package com.yapp.orbit.benchmark + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.ReportDrawnWhen +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.yapp.designsystem.theme.OrbitTheme + +internal const val EXTRA_BENCHMARK_SCREEN = "benchmark_screen_key" +internal const val BENCHMARK_SCREEN_ORBIT_PICKER = "orbit_picker" +internal const val BENCHMARK_UNKNOWN_SCREEN_ROOT = "benchmark_unknown_screen" + +class BenchmarkHostActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val screenKey = intent?.getStringExtra(EXTRA_BENCHMARK_SCREEN) + setContent { + OrbitTheme { + BenchmarkScreenContainer(screenKey) + } + } + } +} + +@Composable +private fun BenchmarkScreenContainer(screenKey: String?) { + when (screenKey) { + BENCHMARK_SCREEN_ORBIT_PICKER -> OrbitPickerBenchmarkScreen() + else -> BenchmarkScreenMissing(screenKey) + } +} + +@Composable +private fun BenchmarkScreenMissing(requestedKey: String?) { + ReportDrawnWhen { true } + Box( + modifier = Modifier + .fillMaxSize() + .background(OrbitTheme.colors.gray_900) + .semantics { contentDescription = BENCHMARK_UNKNOWN_SCREEN_ROOT }, + contentAlignment = Alignment.Center, + ) { + Text( + text = "Benchmark screen '${requestedKey ?: "unknown"}' not registered.", + color = OrbitTheme.colors.white, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + ) + } +} diff --git a/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/OrbitPickerBenchmarkScreen.kt b/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/OrbitPickerBenchmarkScreen.kt new file mode 100644 index 00000000..eae55955 --- /dev/null +++ b/app/src/benchmarkRelease/java/com/yapp/orbit/benchmark/OrbitPickerBenchmarkScreen.kt @@ -0,0 +1,68 @@ +package com.yapp.orbit.benchmark + +import androidx.activity.compose.ReportDrawnWhen +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.component.timepicker.OrbitPicker +import java.time.LocalTime + +internal const val ORBIT_PICKER_BENCHMARK_ROOT = "orbit_picker_root" + +@Composable +internal fun OrbitPickerBenchmarkScreen( + modifier: Modifier = Modifier, + initialTime: LocalTime = LocalTime.of(8, 30), +) { + var selectedTime by remember { mutableStateOf(initialTime) } + var isDrawn by remember { mutableStateOf(false) } + + ReportDrawnWhen { isDrawn } + + LaunchedEffect(Unit) { + isDrawn = true + } + + Box( + modifier = modifier + .fillMaxSize() + .background(OrbitTheme.colors.gray_900) + .semantics { contentDescription = ORBIT_PICKER_BENCHMARK_ROOT }, + contentAlignment = Alignment.Center, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.padding(horizontal = 24.dp), + ) { + OrbitPicker( + initialTime = selectedTime, + onValueChange = { selectedTime = it }, + ) + } + } +} + +@Preview +@Composable +private fun OrbitPickerBenchmarkPreview() { + OrbitTheme { + OrbitPickerBenchmarkScreen() + } +} diff --git a/baselineprofile/src/main/java/com/dongchyeon/baselineprofile/OrbitPickerBenchmarks.kt b/baselineprofile/src/main/java/com/dongchyeon/baselineprofile/OrbitPickerBenchmarks.kt new file mode 100644 index 00000000..87ab0046 --- /dev/null +++ b/baselineprofile/src/main/java/com/dongchyeon/baselineprofile/OrbitPickerBenchmarks.kt @@ -0,0 +1,76 @@ +package com.dongchyeon.baselineprofile + +import android.content.ComponentName +import android.content.Intent +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class OrbitPickerBenchmarks { + + @get:Rule + val rule = MacrobenchmarkRule() + + @Test + fun orbitPickerScrollCompilationNone() = benchmark(CompilationMode.None()) + + @Test + fun orbitPickerScrollCompilationBaselineProfile() = benchmark(CompilationMode.Partial()) + + private fun benchmark(compilationMode: CompilationMode) { + val targetPackage = InstrumentationRegistry.getArguments().getString("targetAppId") + ?: throw IllegalStateException("targetAppId not passed as instrumentation runner arg") + + rule.measureRepeated( + packageName = targetPackage, + metrics = listOf(FrameTimingMetric()), + compilationMode = compilationMode, + startupMode = StartupMode.COLD, + iterations = 10, + setupBlock = { + killProcess() + pressHome() + }, + measureBlock = { + val intent = Intent().apply { + component = ComponentName(targetPackage, BENCHMARK_ACTIVITY_NAME) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(BENCHMARK_SCREEN_EXTRA, ORBIT_PICKER_SCREEN_KEY) + } + + startActivityAndWait(intent) + + device.wait(Until.hasObject(By.desc(ORBIT_PICKER_SEMANTICS)), 5_000) + val picker = device.findObject(By.desc(ORBIT_PICKER_SEMANTICS)) + ?: error("OrbitPicker root not found") + + val bounds = picker.visibleBounds + val x = bounds.centerX() + bounds.width() / 4 + val startY = bounds.centerY() + bounds.height() / 4 + val endY = bounds.centerY() - bounds.height() / 4 + + device.swipe(x, startY, x, endY, 30) + device.waitForIdle() + }, + ) + } + + private companion object { + private const val BENCHMARK_ACTIVITY_NAME = + "com.yapp.orbit.benchmark.BenchmarkHostActivity" + private const val BENCHMARK_SCREEN_EXTRA = "benchmark_screen_key" + private const val ORBIT_PICKER_SCREEN_KEY = "orbit_picker" + private const val ORBIT_PICKER_SEMANTICS = "orbit_picker_root" + } +}