1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.material3.benchmark
18 
19 import androidx.compose.animation.core.snap
20 import androidx.compose.foundation.layout.fillMaxHeight
21 import androidx.compose.foundation.layout.fillMaxWidth
22 import androidx.compose.material.icons.Icons
23 import androidx.compose.material.icons.filled.Add
24 import androidx.compose.material.icons.filled.Check
25 import androidx.compose.material.icons.filled.Edit
26 import androidx.compose.material.icons.filled.Favorite
27 import androidx.compose.material.icons.filled.MoreVert
28 import androidx.compose.material.icons.filled.Person
29 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
30 import androidx.compose.material3.FloatingToolbarDefaults
31 import androidx.compose.material3.HorizontalFloatingToolbar
32 import androidx.compose.material3.Icon
33 import androidx.compose.material3.IconButton
34 import androidx.compose.material3.MaterialTheme
35 import androidx.compose.material3.VerticalFloatingToolbar
36 import androidx.compose.runtime.Composable
37 import androidx.compose.runtime.MutableState
38 import androidx.compose.runtime.mutableStateOf
39 import androidx.compose.runtime.remember
40 import androidx.compose.testutils.LayeredComposeTestCase
41 import androidx.compose.testutils.ToggleableTestCase
42 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
43 import androidx.compose.testutils.benchmark.benchmarkFirstCompose
44 import androidx.compose.testutils.benchmark.benchmarkFirstDraw
45 import androidx.compose.testutils.benchmark.benchmarkFirstLayout
46 import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
47 import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
48 import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
49 import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
50 import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
51 import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
52 import androidx.compose.ui.Modifier
53 import androidx.test.filters.LargeTest
54 import org.junit.Ignore
55 import org.junit.Rule
56 import org.junit.Test
57 import org.junit.runner.RunWith
58 import org.junit.runners.Parameterized
59 
60 @LargeTest
61 @RunWith(Parameterized::class)
62 class FloatingToolbarBenchmark(private val type: FloatingToolbarType) {
63 
64     companion object {
65         @Parameterized.Parameters(name = "{0}")
66         @JvmStatic
parametersnull67         fun parameters() = FloatingToolbarType.values()
68     }
69 
70     @get:Rule val benchmarkRule = ComposeBenchmarkRule()
71 
72     private val mFloatingToolbarTestCaseFactory = { FloatingToolbarTestCase(type) }
<lambda>null73     private val floatingToolbarWithFabTestCaseFactory = { FloatingToolbarWithFabTestCase(type) }
74 
75     @Test
floatingToolbar_firstPixelnull76     fun floatingToolbar_firstPixel() {
77         benchmarkRule.benchmarkToFirstPixel(mFloatingToolbarTestCaseFactory)
78     }
79 
80     @Test
floatingToolbarWithFab_firstPixelnull81     fun floatingToolbarWithFab_firstPixel() {
82         benchmarkRule.benchmarkToFirstPixel(floatingToolbarWithFabTestCaseFactory)
83     }
84 
85     @Ignore
86     @Test
floatingToolbar_first_composenull87     fun floatingToolbar_first_compose() {
88         benchmarkRule.benchmarkFirstCompose(mFloatingToolbarTestCaseFactory)
89     }
90 
91     @Ignore
92     @Test
floatingToolbar_first_measurenull93     fun floatingToolbar_first_measure() {
94         benchmarkRule.benchmarkFirstMeasure(mFloatingToolbarTestCaseFactory)
95     }
96 
97     @Ignore
98     @Test
floatingToolbar_first_layoutnull99     fun floatingToolbar_first_layout() {
100         benchmarkRule.benchmarkFirstLayout(mFloatingToolbarTestCaseFactory)
101     }
102 
103     @Ignore
104     @Test
floatingToolbar_first_drawnull105     fun floatingToolbar_first_draw() {
106         benchmarkRule.benchmarkFirstDraw(mFloatingToolbarTestCaseFactory)
107     }
108 
109     @Test
floatingToolbarWithFab_toggleState_layoutnull110     fun floatingToolbarWithFab_toggleState_layout() {
111         benchmarkRule.toggleStateBenchmarkLayout(
112             caseFactory = floatingToolbarWithFabTestCaseFactory,
113             assertOneRecomposition = false
114         )
115     }
116 
117     @Ignore
118     @Test
floatingToolbarWithFab_toggleState_recomposenull119     fun floatingToolbarWithFab_toggleState_recompose() {
120         benchmarkRule.toggleStateBenchmarkRecompose(
121             caseFactory = floatingToolbarWithFabTestCaseFactory,
122             assertOneRecomposition = false
123         )
124     }
125 
126     @Ignore
127     @Test
floatingToolbarWithFab_toggleState_measurenull128     fun floatingToolbarWithFab_toggleState_measure() {
129         benchmarkRule.toggleStateBenchmarkMeasure(
130             caseFactory = floatingToolbarWithFabTestCaseFactory,
131             assertOneRecomposition = false
132         )
133     }
134 
135     @Ignore
136     @Test
floatingToolbarWithFab_toggleState_drawnull137     fun floatingToolbarWithFab_toggleState_draw() {
138         benchmarkRule.toggleStateBenchmarkDraw(
139             caseFactory = floatingToolbarWithFabTestCaseFactory,
140             assertOneRecomposition = false
141         )
142     }
143 }
144 
145 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
146 internal class FloatingToolbarTestCase(private val type: FloatingToolbarType) :
147     LayeredComposeTestCase() {
148     @Composable
MeasuredContentnull149     override fun MeasuredContent() {
150         when (type) {
151             FloatingToolbarType.Horizontal ->
152                 HorizontalFloatingToolbar(
153                     expanded = true,
154                     modifier = Modifier.fillMaxWidth(),
155                     leadingContent = { LeadingContent() },
156                     trailingContent = { TrailingContent() },
157                     content = { MainContent() },
158                 )
159             FloatingToolbarType.Vertical ->
160                 VerticalFloatingToolbar(
161                     expanded = true,
162                     modifier = Modifier.fillMaxHeight(),
163                     leadingContent = { LeadingContent() },
164                     trailingContent = { TrailingContent() },
165                     content = { MainContent() },
166                 )
167         }
168     }
169 
170     @Composable
171     override fun ContentWrappers(content: @Composable () -> Unit) {
<lambda>null172         MaterialTheme { content() }
173     }
174 }
175 
176 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
177 internal class FloatingToolbarWithFabTestCase(private val type: FloatingToolbarType) :
178     LayeredComposeTestCase(), ToggleableTestCase {
179     private lateinit var expanded: MutableState<Boolean>
180 
181     @Composable
MeasuredContentnull182     override fun MeasuredContent() {
183         expanded = remember { mutableStateOf(false) }
184         when (type) {
185             FloatingToolbarType.Horizontal ->
186                 HorizontalFloatingToolbar(
187                     expanded = expanded.value,
188                     floatingActionButton = { ToolbarFab() },
189                     // Snap the expand and collapse animations.
190                     animationSpec = snap()
191                 ) {
192                     ToolbarContent()
193                 }
194             FloatingToolbarType.Vertical ->
195                 VerticalFloatingToolbar(
196                     expanded = expanded.value,
197                     floatingActionButton = { ToolbarFab() },
198                     // Snap the expand and collapse animations.
199                     animationSpec = snap()
200                 ) {
201                     ToolbarContent()
202                 }
203         }
204     }
205 
206     @Composable
207     override fun ContentWrappers(content: @Composable () -> Unit) {
<lambda>null208         MaterialTheme { content() }
209     }
210 
toggleStatenull211     override fun toggleState() {
212         expanded.value = !expanded.value
213     }
214 }
215 
216 enum class FloatingToolbarType {
217     Horizontal,
218     Vertical,
219 }
220 
221 @Composable
LeadingContentnull222 private fun LeadingContent() {
223     IconButton(onClick = { /* doSomething() */ }) {
224         Icon(Icons.Filled.Add, contentDescription = "Localized description")
225     }
226 }
227 
228 @Composable
TrailingContentnull229 private fun TrailingContent() {
230     IconButton(onClick = { /* doSomething() */ }) {
231         Icon(Icons.Filled.Check, contentDescription = "Localized description")
232     }
233 }
234 
235 @Composable
MainContentnull236 private fun MainContent() {
237     IconButton(onClick = { /* doSomething() */ }) {
238         Icon(Icons.Filled.Edit, contentDescription = "Localized description")
239     }
240 }
241 
242 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
243 @Composable
ToolbarFabnull244 private fun ToolbarFab() {
245     FloatingToolbarDefaults.StandardFloatingActionButton(
246         onClick = { /* doSomething() */ },
247     ) {
248         Icon(Icons.Filled.Check, "Localized description")
249     }
250 }
251 
252 @Composable
ToolbarContentnull253 private fun ToolbarContent() {
254     IconButton(onClick = { /* doSomething() */ }) {
255         Icon(Icons.Filled.Person, contentDescription = "Localized description")
256     }
257     IconButton(onClick = { /* doSomething() */ }) {
258         Icon(Icons.Filled.Edit, contentDescription = "Localized description")
259     }
260     IconButton(onClick = { /* doSomething() */ }) {
261         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
262     }
263     IconButton(
264         onClick = { /* doSomething() */ },
265     ) {
266         Icon(Icons.Filled.MoreVert, contentDescription = "Localized description")
267     }
268 }
269