From bc4f52ed63e12eb8a5d9673fc795c6bc0f93a54e Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 21 Apr 2022 10:45:52 -0700 Subject: [PATCH] [fenix] Added ProfilerStartDialogFragment. --- .../fenix/perf/ProfilerStartDialogFragment.kt | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt diff --git a/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt new file mode 100644 index 0000000000..248bc6d5fe --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.perf + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.fragment.app.activityViewModels +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +import mozilla.components.concept.base.profiler.Profiler +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components + +/** + * Dialogue to start the Gecko profiler in Fenix without the use of ADB. + */ +class ProfilerStartDialogFragment : AppCompatDialogFragment() { + + private lateinit var viewScope: CoroutineScope + + private val delayToPollProfilerForStatus = 100L + private lateinit var profiler: Profiler + private val profilerViewModel: ProfilerViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewScope = MainScope() + + profiler = requireContext().components.core.engine.profiler!! + return ComposeView(requireContext()).apply { + setContent { + StartProfileDialog(context.components.core.engine.profiler!!::startProfiler) + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + viewScope.cancel() + } + + override fun dismiss() { + profilerViewModel.setProfilerState(requireContext().components.core.engine.profiler!!.isProfilerActive()) + super.dismiss() + } + @Composable + private fun StartProfileDialog( + startProfiler: (Array, Array) -> Unit + ) { + val viewStateObserver = remember { mutableStateOf(CardState.ChooseSettings) } + + Dialog( + onDismissRequest = { + // In the wait for profiler state, the user needs to wait for the profiler to start + // so it'd be counterproductive to allow them dismiss the dialog. + if (viewStateObserver.value != CardState.WaitForProfilerToStart) { + this@ProfilerStartDialogFragment.dismiss() + } + } + ) { + if (viewStateObserver.value == CardState.ChooseSettings) { + StartCard(viewStateObserver, startProfiler) + } else { + WaitForProfilerDialog(R.string.profiler_waiting_start) + } + } + } + + @Composable + private fun StartCard( + viewStateObserver: MutableState, + startProfiler: (Array, Array) -> Unit + ) { + val featureAndThreadsObserver = remember { mutableStateOf("") } + ProfilerDialogueCard { + Column(modifier = Modifier.padding(8.dp)) { + Text( + text = stringResource(R.string.preferences_start_profiler), + fontWeight = FontWeight.ExtraBold, + fontSize = 20.sp, + modifier = Modifier.padding(8.dp) + ) + Text( + text = stringResource(R.string.profiler_settings_title), + fontWeight = FontWeight.Bold, + fontSize = 15.sp, + modifier = Modifier.padding(8.dp) + ) + Spacer(modifier = Modifier.height(2.dp)) + ProfilerLabeledRadioButton( + text = stringResource(R.string.profiler_filter_firefox), + subText = stringResource(R.string.profiler_filter_firefox_explain), + state = featureAndThreadsObserver + ) + + ProfilerLabeledRadioButton( + text = stringResource(R.string.profiler_filter_graphics), + subText = stringResource(R.string.profiler_filter_graphics_explain), + state = featureAndThreadsObserver + ) + + ProfilerLabeledRadioButton( + text = stringResource(R.string.profiler_filter_media), + subText = stringResource(R.string.profiler_filter_media_explain), + state = featureAndThreadsObserver + ) + + ProfilerLabeledRadioButton( + text = stringResource(R.string.profiler_filter_networking), + subText = stringResource(R.string.profiler_filter_networking_explain), + state = featureAndThreadsObserver + ) + Spacer(modifier = Modifier.height(8.dp)) + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth() + ) { + TextButton( + onClick = { + this@ProfilerStartDialogFragment.dismiss() + } + ) { + Text(text = stringResource(R.string.profiler_start_cancel)) + } + Spacer(modifier = Modifier.width(4.dp)) + TextButton( + onClick = { + viewStateObserver.value = CardState.WaitForProfilerToStart + executeStartProfilerOnClick( + ProfilerSettings.valueOf(featureAndThreadsObserver.value), + startProfiler + ) + } + ) { + Text(text = stringResource(R.string.preferences_start_profiler)) + } + } + } + } + } + + private fun waitForProfilerActiveAndDismissFragment() { + viewScope.launch { + while (!profiler.isProfilerActive()) { + delay(delayToPollProfilerForStatus) + } + this@ProfilerStartDialogFragment.dismiss() + + val toastString = requireContext().getString(R.string.profiler_start_dialog_started) + Toast.makeText(this@ProfilerStartDialogFragment.context, toastString, Toast.LENGTH_SHORT).show() + } + } + + private fun executeStartProfilerOnClick( + featureAndThreads: ProfilerSettings, + startProfiler: (Array, Array) -> Unit, + ) { + startProfiler(featureAndThreads.threads, featureAndThreads.features) + waitForProfilerActiveAndDismissFragment() + } + + /** + * Card state to change what is displayed in the dialogue + */ + enum class CardState { + ChooseSettings, + WaitForProfilerToStart + } +}