• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 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 package com.example.tracing.demo
17 
18 import android.os.Bundle
19 import android.os.Trace
20 import androidx.activity.ComponentActivity
21 import androidx.activity.compose.setContent
22 import androidx.activity.enableEdgeToEdge
23 import androidx.compose.animation.animateContentSize
24 import androidx.compose.animation.core.Spring
25 import androidx.compose.animation.core.spring
26 import androidx.compose.foundation.layout.Column
27 import androidx.compose.foundation.layout.Row
28 import androidx.compose.foundation.layout.RowScope
29 import androidx.compose.foundation.layout.padding
30 import androidx.compose.foundation.layout.safeDrawingPadding
31 import androidx.compose.foundation.lazy.LazyColumn
32 import androidx.compose.foundation.lazy.items
33 import androidx.compose.material.icons.Icons.Filled
34 import androidx.compose.material.icons.filled.ExpandLess
35 import androidx.compose.material.icons.filled.ExpandMore
36 import androidx.compose.material.icons.filled.PlayCircleOutline
37 import androidx.compose.material.icons.filled.StopCircle
38 import androidx.compose.material3.Card
39 import androidx.compose.material3.CardDefaults
40 import androidx.compose.material3.Icon
41 import androidx.compose.material3.IconButton
42 import androidx.compose.material3.MaterialTheme
43 import androidx.compose.material3.Surface
44 import androidx.compose.material3.Text
45 import androidx.compose.runtime.Composable
46 import androidx.compose.runtime.CompositionLocalProvider
47 import androidx.compose.runtime.compositionLocalOf
48 import androidx.compose.runtime.getValue
49 import androidx.compose.runtime.mutableStateListOf
50 import androidx.compose.runtime.mutableStateOf
51 import androidx.compose.runtime.remember
52 import androidx.compose.runtime.rememberCoroutineScope
53 import androidx.compose.runtime.setValue
54 import androidx.compose.ui.Modifier
55 import androidx.compose.ui.res.stringResource
56 import androidx.compose.ui.text.font.FontFamily
57 import androidx.compose.ui.text.font.FontStyle
58 import androidx.compose.ui.unit.dp
59 import com.android.app.tracing.coroutines.launchTraced as launch
60 import com.example.tracing.demo.experiments.Experiment
61 import com.example.tracing.demo.experiments.TRACK_NAME
62 import com.example.tracing.demo.ui.theme.BasicsCodelabTheme
63 import kotlin.random.Random
64 import kotlinx.coroutines.CancellationException
65 import kotlinx.coroutines.CoroutineDispatcher
66 import kotlinx.coroutines.cancelChildren
67 import kotlinx.coroutines.job
68 
69 val AllExperiments = compositionLocalOf<List<Experiment>> { error("No Experiments found!") }
70 
<lambda>null71 val Experiment = compositionLocalOf<Experiment> { error("No found!") }
72 
73 val ExperimentLaunchDispatcher =
<lambda>null74     compositionLocalOf<CoroutineDispatcher> {
75         error("No @ExperimentLauncher CoroutineDispatcher found!")
76     }
77 
78 class MainActivity(private val appComponent: ApplicationComponent) : ComponentActivity() {
onCreatenull79     override fun onCreate(savedInstanceState: Bundle?) {
80         super.onCreate(savedInstanceState)
81         enableEdgeToEdge()
82         setContent {
83             BasicsCodelabTheme {
84                 CompositionLocalProvider(
85                     AllExperiments provides appComponent.getExperimentList(),
86                     ExperimentLaunchDispatcher provides
87                         appComponent.getExperimentDefaultCoroutineDispatcher(),
88                 ) {
89                     DemoApp(modifier = Modifier.safeDrawingPadding())
90                 }
91             }
92         }
93     }
94 }
95 
96 @Composable
DemoAppnull97 fun DemoApp(modifier: Modifier = Modifier) {
98     Surface(modifier) { ExperimentList() }
99 }
100 
101 @Composable
ExperimentListnull102 private fun ExperimentList(modifier: Modifier = Modifier) {
103     val allExperiments = AllExperiments.current
104 
105     LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
106         items(items = allExperiments.stream().toList()) { experiment ->
107             CompositionLocalProvider(Experiment provides experiment) { ExperimentCard() }
108         }
109     }
110 }
111 
112 @Composable
ExperimentCardnull113 private fun ExperimentCard(modifier: Modifier = Modifier) {
114     Card(
115         modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp),
116         colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary),
117     ) {
118         ExperimentContentRow()
119     }
120 }
121 
122 @Composable
ExperimentContentRownull123 private fun ExperimentContentRow(modifier: Modifier = Modifier) {
124     Row(
125         modifier =
126             modifier
127                 .padding(12.dp)
128                 .animateContentSize(
129                     animationSpec =
130                         spring(
131                             dampingRatio = Spring.DampingRatioNoBouncy,
132                             stiffness = Spring.StiffnessMedium,
133                         )
134                 )
135     ) {
136         ExperimentContent()
137     }
138 }
139 
140 @Composable
ExperimentContentnull141 private fun RowScope.ExperimentContent(modifier: Modifier = Modifier) {
142     val experiment = Experiment.current
143     val launcherDispatcher = ExperimentLaunchDispatcher.current
144     val scope = rememberCoroutineScope { launcherDispatcher + experiment.context }
145 
146     var isRunning by remember { mutableStateOf(false) }
147     var expanded by remember { mutableStateOf(false) }
148     val statusMessages = remember { mutableStateListOf<String>() }
149     val className = experiment.javaClass.simpleName
150 
151     Column(modifier = modifier.weight(1f).padding(12.dp)) {
152         Text(text = experiment.javaClass.simpleName, style = MaterialTheme.typography.headlineSmall)
153         Text(
154             text = experiment.description,
155             style = MaterialTheme.typography.bodyMedium.copy(fontStyle = FontStyle.Italic),
156         )
157         if (expanded) {
158             Text(
159                 text = statusMessages.joinToString(separator = "\n") { it },
160                 style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
161             )
162         }
163     }
164 
165     IconButton(
166         onClick = {
167             if (isRunning) {
168                 scope.coroutineContext.job.cancelChildren()
169             } else {
170                 val cookie = Random.nextInt()
171                 Trace.asyncTraceForTrackBegin(
172                     Trace.TRACE_TAG_APP,
173                     TRACK_NAME,
174                     "Running: $className",
175                     cookie,
176                 )
177                 statusMessages += "Started"
178                 expanded = true
179                 isRunning = true
180                 scope
181                     .launch("$className#runExperiment") { experiment.runExperiment() }
182                     .invokeOnCompletion { cause ->
183                         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, cookie)
184                         isRunning = false
185                         statusMessages +=
186                             when (cause) {
187                                 null -> "completed normally"
188                                 is CancellationException -> "cancelled normally: ${cause.message}"
189                                 else -> "failed"
190                             }
191                     }
192             }
193         }
194     ) {
195         Icon(
196             imageVector = if (isRunning) Filled.StopCircle else Filled.PlayCircleOutline,
197             contentDescription = stringResource(R.string.run_experiment),
198         )
199     }
200     IconButton(
201         onClick = {
202             expanded = !expanded
203             if (!expanded) statusMessages.clear()
204         },
205         enabled = !expanded || !isRunning,
206     ) {
207         Icon(
208             imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
209             contentDescription =
210                 if (expanded) stringResource(R.string.show_less)
211                 else stringResource(R.string.show_more),
212         )
213     }
214 }
215