Foundation of benchmark integration to JCA (#80)
diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index 6abdd98..1f01370 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -3,6 +3,19 @@
<component name="AndroidTestResultsUserPreferences">
<option name="androidTestResultsTableState">
<map>
+ <entry key="-1168588695">
+ <value>
+ <AndroidTestResultsTableState>
+ <option name="preferredColumnWidths">
+ <map>
+ <entry key="Duration" value="90" />
+ <entry key="Pixel_7_Pro_API_34" value="120" />
+ <entry key="Tests" value="360" />
+ </map>
+ </option>
+ </AndroidTestResultsTableState>
+ </value>
+ </entry>
<entry key="401594821">
<value>
<AndroidTestResultsTableState>
@@ -16,6 +29,19 @@
</AndroidTestResultsTableState>
</value>
</entry>
+ <entry key="571770275">
+ <value>
+ <AndroidTestResultsTableState>
+ <option name="preferredColumnWidths">
+ <map>
+ <entry key="Duration" value="90" />
+ <entry key="Pixel_7_Pro_API_34" value="120" />
+ <entry key="Tests" value="360" />
+ </map>
+ </option>
+ </AndroidTestResultsTableState>
+ </value>
+ </entry>
<entry key="2043991187">
<value>
<AndroidTestResultsTableState>
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index ba0df2e..37f7544 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -5,12 +5,14 @@
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
+ <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
- <option name="gradleJvm" value="JDK" />
+ <option name="gradleJvm" value="Android Studio default JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
+ <option value="$PROJECT_DIR$/benchmark" />
<option value="$PROJECT_DIR$/camera-viewfinder-compose" />
<option value="$PROJECT_DIR$/core" />
<option value="$PROJECT_DIR$/core/common" />
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fe988de..9b7c732 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -46,6 +46,11 @@
"proguard-rules.pro"
)
}
+ create("benchmark") {
+ initWith(buildTypes.getByName("release"))
+ signingConfig = signingConfigs.getByName("debug")
+ matchingFallbacks += listOf("release")
+ }
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
@@ -118,6 +123,10 @@
// Settings Screen
implementation(project(":feature:settings"))
+
+ // benchmark
+ implementation("androidx.profileinstaller:profileinstaller:1.3.1")
+
}
// Allow references to generated code
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 40f0301..5e399a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -37,6 +37,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.JetpackCamera"
tools:targetApi="33">
+ <profileable android:shell="true"/>
<activity
android:name=".MainActivity"
android:exported="true"
diff --git a/benchmark/.gitignore b/benchmark/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/benchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
new file mode 100644
index 0000000..bd20c97
--- /dev/null
+++ b/benchmark/build.gradle.kts
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("com.android.test")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.example.benchmark"
+ compileSdk = 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+ defaultConfig {
+ //Our app has a minSDK of 21, but in order for the benchmark tool to function, it must be 23
+ minSdk = 23
+ targetSdk = 34
+
+ // allows the benchmark to be run on an emulator
+ testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] =
+ "EMULATOR,LOW-BATTERY,NOT-PROFILEABLE"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ 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.
+ create("benchmark") {
+ isDebuggable = true
+ signingConfig = getByName("debug").signingConfig
+ matchingFallbacks += listOf("release")
+ }
+ }
+
+ targetProjectPath = ":app"
+ experimentalProperties["android.experimental.self-instrumenting"] = true
+ // required for benchmark:
+ // self instrumentation required for the tests to be able to compile, start, or kill the app
+ // ensures test and app processes are separate
+ // see https://source.android.com/docs/core/tests/development/instr-self-e2e
+}
+
+dependencies {
+ implementation("androidx.test.ext:junit:1.1.5")
+ implementation("androidx.benchmark:benchmark-macro-junit4:1.2.1")
+}
+
+androidComponents {
+ beforeVariants(selector().all()) {
+ it.enable = it.buildType == "benchmark"
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0370d89
--- /dev/null
+++ b/benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest />
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/example/benchmark/Permissions.kt b/benchmark/src/main/java/com/example/benchmark/Permissions.kt
new file mode 100644
index 0000000..9a6d595
--- /dev/null
+++ b/benchmark/src/main/java/com/example/benchmark/Permissions.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.benchmark
+
+import android.Manifest.permission
+import android.os.Build
+import androidx.benchmark.macro.MacrobenchmarkScope
+import org.junit.Assert
+
+fun MacrobenchmarkScope.allowCamera() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val command = "pm grant $packageName ${permission.CAMERA}"
+ val output = device.executeShellCommand(command)
+ Assert.assertEquals("", output)
+ }
+}
diff --git a/benchmark/src/main/java/com/example/benchmark/StartupBenchmark.kt b/benchmark/src/main/java/com/example/benchmark/StartupBenchmark.kt
new file mode 100644
index 0000000..34ffd3e
--- /dev/null
+++ b/benchmark/src/main/java/com/example/benchmark/StartupBenchmark.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.benchmark
+
+import androidx.benchmark.macro.MacrobenchmarkScope
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This is an example startup benchmark.
+ *
+ * It navigates to the device's home screen, and launches the default activity.
+ *
+ * Before running this benchmark:
+ * 1) switch your app's active build variant in the Studio (affects Studio runs only)
+ * 2) add `<profileable android:shell="true" />` to your app's manifest, within the `<application>` tag
+ *
+ * Run this benchmark from Studio to see startup measurements, and captured system traces
+ * for investigating your app's performance.
+ */
+@RunWith(AndroidJUnit4::class)
+class StartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun startupNoCameraPermission() {
+ benchmarkStartup()
+ }
+
+ @Test
+ fun startupWithCameraPermission() {
+ benchmarkStartup(
+ setupBlock =
+ { allowCamera() }
+ )
+ }
+
+ private fun benchmarkStartup(setupBlock: MacrobenchmarkScope.() -> Unit = {}) {
+ benchmarkRule.measureRepeated(
+ packageName = "com.google.jetpackcamera",
+ metrics = listOf(StartupTimingMetric()),
+ iterations = 5,
+ startupMode = StartupMode.COLD,
+ setupBlock = setupBlock
+
+ ) {
+ pressHome()
+ startActivityAndWait()
+ }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index d22d8a2..0b240f9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -20,6 +20,7 @@
id("com.android.library") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.0" apply false
id("com.google.dagger.hilt.android") version "2.44" apply false
+ id("com.android.test") version "8.1.1" apply false
}
tasks.register<Copy>("installGitHooks") {
diff --git a/data/settings/consumer-rules.pro b/data/settings/consumer-rules.pro
index e69de29..2b9ca75 100644
--- a/data/settings/consumer-rules.pro
+++ b/data/settings/consumer-rules.pro
@@ -0,0 +1 @@
+-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* {<fields>;}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5e4c892..ec036b0 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -40,3 +40,4 @@
include(":data:settings")
include(":core:common")
include(":feature:quicksettings")
+include(":benchmark")