Compose Pager Benchmarks
Added macro-benchmarks for Pager and ViewPager2 scroll events.
Relnote: N/A
Test: N/A
Change-Id: I3ccb28c3351107bfc90765e0dc10b5eb983785bd
diff --git a/compose/integration-tests/macrobenchmark-target/build.gradle b/compose/integration-tests/macrobenchmark-target/build.gradle
index 8b2e8c1..e1368a6 100644
--- a/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -19,6 +19,7 @@
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation(libs.kotlinStdlib)
implementation(project(":activity:activity-compose"))
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 25ddd6e..e228203 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -111,6 +111,27 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity
+ android:name=".ViewPagerActivity"
+ android:exported="true"
+ android:theme="@style/Theme.AppCompat">
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.VIEW_PAGER_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".PagerActivity"
+ android:exported="true"
+ android:theme="@style/Theme.AppCompat">
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.LAZY_PAGER_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity
android:name=".TrivialTracingActivity"
android:exported="true">
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerActivity.kt
new file mode 100644
index 0000000..3ffe581
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerActivity.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 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 androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+class PagerActivity : ComponentActivity() {
+ @OptIn(ExperimentalFoundationApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val itemCount = intent.getIntExtra(ExtraItemCount, 3000)
+
+ setContent {
+ val pagerState = rememberPagerState()
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ HorizontalPager(
+ modifier = Modifier
+ .semantics { contentDescription = "Pager" }
+ .background(Color.White),
+ state = pagerState,
+ pageCount = itemCount
+ ) {
+ PagerItem(it)
+ }
+ }
+ }
+
+ launchIdlenessTracking()
+ }
+
+ companion object {
+ const val ExtraItemCount = "ITEM_COUNT"
+ }
+}
+
+@Composable
+private fun PagerItem(index: Int) {
+ Box(
+ modifier = Modifier
+ .size(200.dp, 400.dp)
+ .background(Color.Black),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(text = index.toString(), color = Color.White)
+ }
+}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/ViewPagerActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/ViewPagerActivity.kt
new file mode 100644
index 0000000..b1be881
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/ViewPagerActivity.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2022 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 androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.PagerSnapHelper
+import androidx.recyclerview.widget.RecyclerView
+
+class ViewPagerActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_view_pager)
+ val pager = findViewById<RecyclerView>(R.id.pager)
+ pager.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
+ val itemCount = intent.getIntExtra(ExtraItemCount, 3000)
+ val adapter = PagerAdapter(itemCount)
+ val scroller = PagerSnapHelper()
+ scroller.attachToRecyclerView(pager)
+ pager.adapter = adapter
+ launchIdlenessTracking()
+ }
+
+ companion object {
+ const val ExtraItemCount = "ITEM_COUNT"
+ }
+}
+
+private class PagerAdapter(val items: Int) : RecyclerView.Adapter<PagerViewHolder>() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
+ val view = LayoutInflater
+ .from(parent.context)
+ .inflate(R.layout.view_pager_item, parent, false)
+
+ return PagerViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
+ holder.bind(position.toString())
+ }
+
+ override fun getItemCount(): Int = items
+}
+
+private class PagerViewHolder(val itemView: View) : RecyclerView.ViewHolder(itemView) {
+ fun bind(item: String) {
+ itemView.findViewById<TextView>(R.id.view_pager_item).text = item
+ }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_pager.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_pager.xml
new file mode 100644
index 0000000..1ce3f46
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_pager.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2022 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#fff"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.recyclerview.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+</FrameLayout>
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/view_pager_item.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/view_pager_item.xml
new file mode 100644
index 0000000..33e2e5c
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/view_pager_item.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2022 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="200dp"
+ android:background="#000000"
+ android:layout_height="400dp"
+ android:layout_gravity="center">
+
+ <TextView
+ android:id="@+id/view_pager_item"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</FrameLayout>
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/AndroidViewPagerBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/AndroidViewPagerBenchmark.kt
new file mode 100644
index 0000000..916b3cf
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/AndroidViewPagerBenchmark.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022 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 androidx.compose.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Direction
+import androidx.test.uiautomator.UiDevice
+import androidx.testutils.createCompilationParams
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class AndroidViewPagerBenchmark(
+ private val compilationMode: CompilationMode
+) {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+ private lateinit var device: UiDevice
+
+ @Before
+ fun setUp() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ device = UiDevice.getInstance(instrumentation)
+ }
+
+ @Test
+ fun scroll() {
+ benchmarkRule.measureRepeated(
+ packageName = PackageName,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = compilationMode,
+ iterations = 10,
+ setupBlock = {
+ val intent = Intent()
+ intent.action = Action
+ startActivityAndWait(intent)
+ }
+ ) {
+ val pager = device.findObject(
+ By.res(
+ PackageName,
+ ResourceId
+ )
+ )
+ // Setting a gesture margin is important otherwise gesture nav is triggered.
+ pager.setGestureMargin(device.displayWidth / 5)
+ repeat(10) {
+ // From center we scroll 2/3 of it which is 1/3 of the screen.
+ pager.swipe(Direction.LEFT, 1.0f)
+ device.waitForIdle()
+ }
+ }
+ }
+
+ companion object {
+ private const val PackageName = "androidx.compose.integration.macrobenchmark.target"
+ private const val Action =
+ "androidx.compose.integration.macrobenchmark.target.VIEW_PAGER_ACTIVITY"
+ private const val ResourceId = "pager"
+
+ @Parameterized.Parameters(name = "compilation={0}")
+ @JvmStatic
+ fun parameters() = createCompilationParams()
+ }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerBenchmark.kt
new file mode 100644
index 0000000..cca2bba
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerBenchmark.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 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 androidx.compose.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Direction
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import androidx.testutils.createCompilationParams
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class PagerBenchmark(
+ private val compilationMode: CompilationMode
+) {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ private lateinit var device: UiDevice
+
+ @Before
+ fun setUp() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ device = UiDevice.getInstance(instrumentation)
+ }
+
+ @Test
+ fun scroll() {
+ benchmarkRule.measureRepeated(
+ packageName = PackageName,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = compilationMode,
+ iterations = 10,
+ setupBlock = {
+ val intent = Intent()
+ intent.action = Action
+ startActivityAndWait(intent)
+ }
+ ) {
+ val pager = device.findObject(By.desc(ContentDescription))
+ // Setting a gesture margin is important otherwise gesture nav is triggered.
+ pager.setGestureMargin(device.displayWidth / 5)
+ for (i in 1..10) {
+ // From center we scroll 2/3 of it which is 1/3 of the screen.
+ pager.swipe(Direction.LEFT, 1.0f)
+ device.wait(Until.findObject(By.desc(ComposeIdle)), 3000)
+ }
+ }
+ }
+
+ companion object {
+ private const val PackageName = "androidx.compose.integration.macrobenchmark.target"
+ private const val Action =
+ "androidx.compose.integration.macrobenchmark.target.LAZY_PAGER_ACTIVITY"
+ private const val ContentDescription = "Pager"
+ private const val ComposeIdle = "COMPOSE-IDLE"
+
+ @Parameterized.Parameters(name = "compilation={0}")
+ @JvmStatic
+ fun parameters() = createCompilationParams()
+ }
+}
\ No newline at end of file