diff --git a/app-watch-benchmark/.gitignore b/app-watch-benchmark/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app-watch-benchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app-watch-benchmark/build.gradle.kts b/app-watch-benchmark/build.gradle.kts
new file mode 100644
index 0000000..f7bf90a
--- /dev/null
+++ b/app-watch-benchmark/build.gradle.kts
@@ -0,0 +1,54 @@
+@file:Suppress("UnstableApiUsage")
+
+plugins {
+ kotlin("android")
+ id("com.android.test")
+}
+
+android {
+ namespace = "com.louiscad.composeoclockplayground.benchmark"
+
+ defaultConfig {
+ minSdk = 30
+ compileSdk = 34
+ targetSdk = 34
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunnerArguments["androidx.benchmark.profiling.mode"] = "none"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ buildTypes {
+ // This benchmark buildType is used for benchmarking, and should function like your
+ // release build (for example, with minification on). It's signed with a debug key
+ // for easy local/CI testing.
+ val benchmark by creating {
+ isDebuggable = false
+ signingConfig = signingConfigs.getByName("debug")
+ matchingFallbacks.add("release")
+ }
+ }
+
+ targetProjectPath = ":app-watch"
+ experimentalProperties["android.experimental.self-instrumenting"] = true
+}
+
+dependencies {
+ implementation {
+ platform(AndroidX.compose.bom)
+ AndroidX.compose.ui.testJunit4()
+ AndroidX.test.runner()
+ AndroidX.test.rules()
+ AndroidX.test.uiAutomator()
+ AndroidX.benchmark.macroJunit4()
+ }
+}
+
+androidComponents {
+ beforeVariants {
+ it.enable = it.buildType == "benchmark"
+ }
+}
diff --git a/app-watch-benchmark/src/main/AndroidManifest.xml b/app-watch-benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4bacb4f
--- /dev/null
+++ b/app-watch-benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt
new file mode 100644
index 0000000..7942f4b
--- /dev/null
+++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt
@@ -0,0 +1,41 @@
+package com.louiscad.composeoclockplayground.benchmark
+
+import android.app.UiAutomation
+import android.content.ComponentName
+import android.view.KeyEvent
+import androidx.test.uiautomator.UiDevice
+
+fun UiDevice.startWatchface(watchfaceName: ComponentName) {
+ // From https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/deployer/src/main/java/com/android/tools/deployer/model/component/WatchFace.java
+ val result =
+ executeShellCommand("am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE --es operation set-watchface --ecn component ${watchfaceName.flattenToString()}")
+
+ // TODO error checking
+}
+
+fun UiDevice.currentWatchface(): String {
+ return executeShellCommand("am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE --es operation current-watchface")
+}
+
+val DefaultWatchFace = ComponentName(
+ "com.google.android.wearable.sysui",
+ "com.google.android.clockwork.sysui.experiences.defaultwatchface.DefaultWatchFace"
+)
+
+inline fun UiAutomation.withShellPermission(block: () -> Unit) {
+ adoptShellPermissionIdentity()
+
+ try {
+ block()
+ } finally {
+ dropShellPermissionIdentity()
+ }
+}
+
+fun UiDevice.pressSleep() {
+ pressKeyCode(KeyEvent.KEYCODE_SLEEP)
+}
+
+fun UiDevice.pressWakeup() {
+ pressKeyCode(KeyEvent.KEYCODE_WAKEUP)
+}
diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt
new file mode 100644
index 0000000..b2d712b
--- /dev/null
+++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt
@@ -0,0 +1,17 @@
+@file:OptIn(ExperimentalMetricApi::class, ExperimentalPerfettoTraceProcessorApi::class)
+
+package com.louiscad.composeoclockplayground.benchmark
+
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.TraceMetric
+import androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi
+import androidx.benchmark.perfetto.PerfettoTraceProcessor
+
+object IgnoredMetric : TraceMetric() {
+ override fun getResult(
+ captureInfo: CaptureInfo,
+ traceSession: PerfettoTraceProcessor.Session
+ ): List {
+ return listOf(Measurement("ignored", 0.0))
+ }
+}
\ No newline at end of file
diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt
new file mode 100644
index 0000000..8a397e0
--- /dev/null
+++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt
@@ -0,0 +1,10 @@
+package com.louiscad.composeoclockplayground.benchmark
+
+import android.content.ComponentName
+
+class SampleWatchfaceBenchmark : WatchFaceBenchmark() {
+ override val watchfaceComponent: ComponentName = ComponentName(
+ PACKAGE_NAME,
+ "com.louiscad.composeoclockplayground.SampleWatchFaceService"
+ )
+}
\ No newline at end of file
diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt
new file mode 100644
index 0000000..9384102
--- /dev/null
+++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt
@@ -0,0 +1,72 @@
+package com.louiscad.composeoclockplayground.benchmark
+
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.ComponentName
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class WatchFaceBenchmark {
+ private lateinit var instrumentation: Instrumentation
+ private lateinit var uiAutomation: UiAutomation
+ private lateinit var device: UiDevice
+
+ abstract val watchfaceComponent: ComponentName
+
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Before
+ fun setup() {
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ uiAutomation = instrumentation.uiAutomation
+ device = UiDevice.getInstance(instrumentation)
+ }
+
+ @Before
+ fun after() {
+ device.wakeUp()
+ device.startWatchface(DefaultWatchFace)
+ }
+
+ @Test
+ fun oneMinuteInteractive() = benchmarkRule.measureRepeated(
+ packageName = watchfaceComponent.packageName,
+ metrics = metrics(),
+ compilationMode = CompilationMode.Partial(),
+ // TODO bump to multiple
+ iterations = 1,
+ startupMode = StartupMode.WARM,
+ setupBlock = {
+ device.startWatchface(watchfaceComponent)
+ repeat(5) {
+ println("Sleep in setupBlock $it")
+ Thread.sleep(1000)
+ }
+
+ println("Waking Up")
+ device.wakeUp()
+ },
+ ) {
+ repeat(10) {
+ device.wakeUp()
+ println("Sleep in test $it")
+ Thread.sleep(6000)
+ }
+ }
+
+ // TODO add interesting watchface metrics
+ open fun metrics() = listOf(IgnoredMetric)
+
+ companion object {
+ val PACKAGE_NAME = "com.louiscad.composeoclockplayground"
+ }
+}
+
+
diff --git a/app-watch/proguard-benchmark.pro b/app-watch/proguard-benchmark.pro
new file mode 100644
index 0000000..4ce9fce
--- /dev/null
+++ b/app-watch/proguard-benchmark.pro
@@ -0,0 +1,3 @@
+# When generating the baseline profile we want the proper names of
+# the methods and classes
+-dontobfuscate
\ No newline at end of file
diff --git a/convention-plugins/src/main/kotlin/android-app.gradle.kts b/convention-plugins/src/main/kotlin/android-app.gradle.kts
index b5a040b..5934556 100644
--- a/convention-plugins/src/main/kotlin/android-app.gradle.kts
+++ b/convention-plugins/src/main/kotlin/android-app.gradle.kts
@@ -58,6 +58,14 @@ android {
"proguard-rules.pro"
)
}
+ create("benchmark") {
+ initWith(buildTypes.getByName("release"))
+ matchingFallbacks += "release"
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-benchmark.pro"
+ )
+ }
}
kotlinOptions {
freeCompilerArgs += "-opt-in=splitties.experimental.ExperimentalSplittiesApi"
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 25dceae..df9f474 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -46,4 +46,5 @@ include {
"app-phone"()
"app-watch"()
"shared"()
+ "app-watch-benchmark"()
}
diff --git a/versions.properties b/versions.properties
index e5db9f6..c143ded 100644
--- a/versions.properties
+++ b/versions.properties
@@ -19,6 +19,8 @@ version.androidx.activity=1.8.2
## # available=1.9.0-alpha01
## # available=1.9.0-alpha02
+version.androidx.benchmark=1.3.0-alpha01
+
version.androidx.compose=2023.10.01
version.androidx.compose.compiler=1.5.8
@@ -72,6 +74,11 @@ version.androidx.test.ext.junit=1.1.5
## # available=1.2.0-alpha02
## # available=1.2.0-alpha03
+version.androidx.test.rules=1.5.0
+## # available=1.6.0-alpha01
+## # available=1.6.0-alpha02
+## # available=1.6.0-alpha03
+
version.androidx.test.runner=1.5.2
## # available=1.5.3-alpha01
## # available=1.6.0-alpha01
@@ -81,6 +88,8 @@ version.androidx.test.runner=1.5.2
## # available=1.6.0-alpha05
## # available=1.6.0-alpha06
+version.androidx.test.uiautomator=2.3.0
+
version.androidx.wear.compose=1.3.0
## # available=1.4.0-alpha01