Creating benchmarks to monitor the Carrousel Pager use case.

Create a carrousel-like test case for views (using recycler view and SnapHelper) and a Compose Pager with fixed page size.

Test: N/A
Change-Id: I4512dabe47e2434dea2fe893a2e00a463a5642f5
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index e228203..8d6b34e 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -34,10 +34,10 @@
         <activity
             android:name=".TrivialStartupActivity"
             android:exported="true">
-<!--            <intent-filter>-->
-<!--                <action android:name="android.intent.action.MAIN" />-->
-<!--                <category android:name="android.intent.category.LAUNCHER" />-->
-<!--            </intent-filter>-->
+            <!--            <intent-filter>-->
+            <!--                <action android:name="android.intent.action.MAIN" />-->
+            <!--                <category android:name="android.intent.category.LAUNCHER" />-->
+            <!--            </intent-filter>-->
             <intent-filter>
                 <action android:name="androidx.compose.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -54,10 +54,10 @@
         <activity
             android:name=".IoSettingsActivity"
             android:exported="true">
-<!--            <intent-filter>-->
-<!--                <action android:name="android.intent.action.MAIN" />-->
-<!--                <category android:name="android.intent.category.LAUNCHER" />-->
-<!--            </intent-filter>-->
+            <!--            <intent-filter>-->
+            <!--                <action android:name="android.intent.action.MAIN" />-->
+            <!--                <category android:name="android.intent.category.LAUNCHER" />-->
+            <!--            </intent-filter>-->
             <intent-filter>
                 <action android:name="androidx.compose.integration.macrobenchmark.target.IO_SETTINGS_ACTIVITY" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -123,6 +123,26 @@
         </activity>
 
         <activity
+            android:name=".RecyclerViewAsCarouselActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.RecyclerViewAsCarouselActivity" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".PagerAsCarouselActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.PagerAsCarouselActivity" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
             android:name=".PagerActivity"
             android:exported="true"
             android:theme="@style/Theme.AppCompat">
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerAsCarouselActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerAsCarouselActivity.kt
new file mode 100644
index 0000000..50fc66b
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerAsCarouselActivity.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 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 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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PageSize
+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 PagerAsCarouselActivity : 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()
+                    .background(Color.White),
+                contentAlignment = Alignment.Center
+            ) {
+                HorizontalPager(
+                    modifier = Modifier
+                        .semantics { contentDescription = "Carousel" }
+                        .background(Color.White),
+                    state = pagerState,
+                    pageCount = itemCount,
+                    pageSize = PageSize.Fixed(200.dp)
+                ) {
+                    PagerItem(it)
+                }
+            }
+        }
+
+        launchIdlenessTracking()
+    }
+
+    companion object {
+        const val ExtraItemCount = "ITEM_COUNT"
+    }
+}
+
+@Composable
+private fun PagerItem(index: Int) {
+    Box(
+        modifier = Modifier
+            .fillMaxWidth()
+            .height(200.dp)
+            .background(Color.Black)
+    ) {
+        Text(text = index.toString(), color = Color.White)
+    }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/RecyclerViewAsCarouselActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/RecyclerViewAsCarouselActivity.kt
new file mode 100644
index 0000000..3766e35
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/RecyclerViewAsCarouselActivity.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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 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 RecyclerViewAsCarouselActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_view_carousel)
+        val pager = findViewById<RecyclerView>(R.id.carousel)
+        pager.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
+        val itemCount = intent.getIntExtra(ExtraItemCount, 3000)
+        val adapter = RecyclerViewAdapter(itemCount)
+        val scroller = PagerSnapHelper()
+        scroller.attachToRecyclerView(pager)
+        pager.adapter = adapter
+        launchIdlenessTracking()
+    }
+
+    companion object {
+        const val ExtraItemCount = "ITEM_COUNT"
+    }
+}
+
+private class RecyclerViewAdapter(val items: Int) :
+    RecyclerView.Adapter<RecyclerViewAsPagerViewHolder>() {
+    override fun onCreateViewHolder(
+        parent: ViewGroup,
+        viewType: Int
+    ): RecyclerViewAsPagerViewHolder {
+        val view = LayoutInflater
+            .from(parent.context)
+            .inflate(R.layout.recycler_view_as_carousel_item, parent, false)
+
+        return RecyclerViewAsPagerViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: RecyclerViewAsPagerViewHolder, position: Int) {
+        holder.bind(position.toString())
+    }
+
+    override fun getItemCount(): Int = items
+}
+
+private class RecyclerViewAsPagerViewHolder(val itemView: View) :
+    RecyclerView.ViewHolder(itemView) {
+    fun bind(item: String) {
+        itemView.findViewById<TextView>(R.id.view_carousel_item).text = item
+    }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml
new file mode 100644
index 0000000..61e862c
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 20233 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="400dp">
+
+    <androidx.viewpager2.widget.ViewPager2
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/carousel"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</FrameLayout>
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml
new file mode 100644
index 0000000..03eb6bd
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="200dp"
+    android:background="#000000"
+    android:layout_height="200dp"
+    android:layout_gravity="center">
+
+    <TextView
+        android:id="@+id/view_carousel_item"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</FrameLayout>
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
new file mode 100644
index 0000000..f28d5ac
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 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 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.UiObject2
+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 PagerAsCarouselBenchmark(
+    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() {
+        val carousel = device.findObject(By.desc(ContentDescription))
+        benchmarkRule.performRepeatedScroll(PackageName, compilationMode, Action, carousel) {
+            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.PagerAsCarouselActivity"
+        private const val ContentDescription = "Carousel"
+        private const val ComposeIdle = "COMPOSE-IDLE"
+
+        @Parameterized.Parameters(name = "compilation={0}")
+        @JvmStatic
+        fun parameters() = createCompilationParams()
+    }
+}
+
+internal fun MacrobenchmarkRule.performRepeatedScroll(
+    packageName: String,
+    compilationMode: CompilationMode,
+    intentAction: String,
+    targetComponent: UiObject2,
+    repeatCount: Int = 10,
+    onSwipeFinished: () -> Unit
+) {
+    measureRepeated(
+        packageName = packageName,
+        metrics = listOf(FrameTimingMetric()),
+        compilationMode = compilationMode,
+        iterations = 10,
+        setupBlock = {
+            val intent = Intent()
+            intent.action = intentAction
+            startActivityAndWait(intent)
+        }
+    ) {
+        // Setting a gesture margin is important otherwise gesture nav is triggered.
+        targetComponent.setGestureMargin(device.displayWidth / 5)
+        for (i in 1..repeatCount) {
+            targetComponent.swipe(Direction.LEFT, 1.0f)
+            onSwipeFinished()
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt
new file mode 100644
index 0000000..ac953f6
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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 androidx.compose.integration.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+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.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 RecyclerViewAsCarouselBenchmark(
+    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() {
+        val carousel = device.findObject(
+            By.res(
+                PackageName,
+                ResourceId
+            )
+        )
+        benchmarkRule.performRepeatedScroll(PackageName, compilationMode, Action, carousel) {
+            device.waitForIdle()
+        }
+    }
+
+    companion object {
+        private const val PackageName = "androidx.compose.integration.macrobenchmark.target"
+        private const val Action =
+            "androidx.compose.integration.macrobenchmark.target.RecyclerViewAsCarouselActivity"
+        private const val ResourceId = "carousel"
+
+        @Parameterized.Parameters(name = "compilation={0}")
+        @JvmStatic
+        fun parameters() = createCompilationParams()
+    }
+}
\ No newline at end of file