Loading packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -748,6 +748,7 @@ android_library { "//frameworks/libs/systemui:motion_tool_lib", "//frameworks/libs/systemui:contextualeducationlib", "androidx.core_core-animation-testing", "androidx.lifecycle_lifecycle-runtime-testing", "androidx.compose.ui_ui", "flag-junit", "ravenwood-junit", Loading Loading @@ -789,6 +790,7 @@ android_library { "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "androidx.lifecycle_lifecycle-runtime-testing", "mockito-target-extended-minus-junit4", "mockito-kotlin-nodeps", "androidx.test.ext.junit", Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +3 −8 Original line number Diff line number Diff line Loading @@ -29,8 +29,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment Loading @@ -42,6 +40,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.lifecycle.rememberViewModel Loading Loading @@ -114,7 +113,7 @@ constructor( } @Composable private fun ShadeBody( fun ShadeBody( viewModel: QuickSettingsContainerViewModel, ) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() Loading @@ -131,6 +130,7 @@ private fun ShadeBody( } else { QuickSettingsLayout( viewModel = viewModel, modifier = Modifier.sysuiResTag("quick_settings_panel") ) } } Loading Loading @@ -158,11 +158,6 @@ private fun QuickSettingsLayout( Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), viewModel.editModeViewModel::startEditing, ) Button( onClick = { viewModel.editModeViewModel.startEditing() }, ) { Text("Edit mode") } } } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 com.android.systemui.qs.composefragment.viewmodel import android.content.testableContext import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.qs.fgsManagerController import com.android.systemui.res.R import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) class QSFragmentComposeViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val lifecycleOwner = TestLifecycleOwner( initialState = Lifecycle.State.CREATED, coroutineDispatcher = kosmos.testDispatcher, ) private val underTest by lazy { kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope) } @Before fun setUp() { Dispatchers.setMain(kosmos.testDispatcher) } @After fun teardown() { Dispatchers.resetMain() } // For now the state changes at 0.5f expansion. This will change once we implement animation // (and this test will fail) @Test fun qsExpansionValueChanges_correctExpansionState() = with(kosmos) { testScope.testWithinLifecycle { val expansionState by collectLastValue(underTest.expansionState) underTest.qsExpansionValue = 0f assertThat(expansionState) .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) underTest.qsExpansionValue = 0.3f assertThat(expansionState) .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) underTest.qsExpansionValue = 0.7f assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) underTest.qsExpansionValue = 1f assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) } } @Test fun qqsHeaderHeight_largeScreenHeader_0() = with(kosmos) { testScope.testWithinLifecycle { val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, true ) fakeConfigurationRepository.onConfigurationChange() assertThat(qqsHeaderHeight).isEqualTo(0) } } @Test fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() = with(kosmos) { testScope.testWithinLifecycle { val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, false ) fakeConfigurationRepository.onConfigurationChange() assertThat(qqsHeaderHeight) .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) } } @Test fun footerActionsControllerInit() = with(kosmos) { testScope.testWithinLifecycle { underTest runCurrent() assertThat(fgsManagerController.initialized).isTrue() } } private inline fun TestScope.testWithinLifecycle( crossinline block: suspend TestScope.() -> TestResult ): TestResult { return runTest { lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED) block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) } } } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt +2 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading Loading @@ -59,7 +60,7 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { private val footerActionsViewModel = mock<FooterActionsViewModel>() private val footerActionsViewModelFactory = mock<FooterActionsViewModel.Factory> { whenever(create(any())).thenReturn(footerActionsViewModel) whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel) } private val footerActionsController = mock<FooterActionsController>() Loading packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt +7 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.qs import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentService import com.android.systemui.qs.composefragment.QSFragmentCompose import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey Loading @@ -31,13 +32,18 @@ class QSFragmentStartable @Inject constructor( private val fragmentService: FragmentService, private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy> private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>, private val qsFragmentComposeProvider: Provider<QSFragmentCompose>, ) : CoreStartable { override fun start() { fragmentService.addFragmentInstantiationProvider( QSFragmentLegacy::class.java, qsFragmentLegacyProvider ) fragmentService.addFragmentInstantiationProvider( QSFragmentCompose::class.java, qsFragmentComposeProvider ) } } Loading Loading
packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -748,6 +748,7 @@ android_library { "//frameworks/libs/systemui:motion_tool_lib", "//frameworks/libs/systemui:contextualeducationlib", "androidx.core_core-animation-testing", "androidx.lifecycle_lifecycle-runtime-testing", "androidx.compose.ui_ui", "flag-junit", "ravenwood-junit", Loading Loading @@ -789,6 +790,7 @@ android_library { "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "androidx.lifecycle_lifecycle-runtime-testing", "mockito-target-extended-minus-junit4", "mockito-kotlin-nodeps", "androidx.test.ext.junit", Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +3 −8 Original line number Diff line number Diff line Loading @@ -29,8 +29,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment Loading @@ -42,6 +40,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.lifecycle.rememberViewModel Loading Loading @@ -114,7 +113,7 @@ constructor( } @Composable private fun ShadeBody( fun ShadeBody( viewModel: QuickSettingsContainerViewModel, ) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() Loading @@ -131,6 +130,7 @@ private fun ShadeBody( } else { QuickSettingsLayout( viewModel = viewModel, modifier = Modifier.sysuiResTag("quick_settings_panel") ) } } Loading Loading @@ -158,11 +158,6 @@ private fun QuickSettingsLayout( Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), viewModel.editModeViewModel::startEditing, ) Button( onClick = { viewModel.editModeViewModel.startEditing() }, ) { Text("Edit mode") } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 com.android.systemui.qs.composefragment.viewmodel import android.content.testableContext import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.qs.fgsManagerController import com.android.systemui.res.R import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) class QSFragmentComposeViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val lifecycleOwner = TestLifecycleOwner( initialState = Lifecycle.State.CREATED, coroutineDispatcher = kosmos.testDispatcher, ) private val underTest by lazy { kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope) } @Before fun setUp() { Dispatchers.setMain(kosmos.testDispatcher) } @After fun teardown() { Dispatchers.resetMain() } // For now the state changes at 0.5f expansion. This will change once we implement animation // (and this test will fail) @Test fun qsExpansionValueChanges_correctExpansionState() = with(kosmos) { testScope.testWithinLifecycle { val expansionState by collectLastValue(underTest.expansionState) underTest.qsExpansionValue = 0f assertThat(expansionState) .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) underTest.qsExpansionValue = 0.3f assertThat(expansionState) .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) underTest.qsExpansionValue = 0.7f assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) underTest.qsExpansionValue = 1f assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) } } @Test fun qqsHeaderHeight_largeScreenHeader_0() = with(kosmos) { testScope.testWithinLifecycle { val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, true ) fakeConfigurationRepository.onConfigurationChange() assertThat(qqsHeaderHeight).isEqualTo(0) } } @Test fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() = with(kosmos) { testScope.testWithinLifecycle { val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, false ) fakeConfigurationRepository.onConfigurationChange() assertThat(qqsHeaderHeight) .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) } } @Test fun footerActionsControllerInit() = with(kosmos) { testScope.testWithinLifecycle { underTest runCurrent() assertThat(fgsManagerController.initialized).isTrue() } } private inline fun TestScope.testWithinLifecycle( crossinline block: suspend TestScope.() -> TestResult ): TestResult { return runTest { lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED) block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) } } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt +2 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading Loading @@ -59,7 +60,7 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { private val footerActionsViewModel = mock<FooterActionsViewModel>() private val footerActionsViewModelFactory = mock<FooterActionsViewModel.Factory> { whenever(create(any())).thenReturn(footerActionsViewModel) whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel) } private val footerActionsController = mock<FooterActionsController>() Loading
packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt +7 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.qs import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentService import com.android.systemui.qs.composefragment.QSFragmentCompose import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey Loading @@ -31,13 +32,18 @@ class QSFragmentStartable @Inject constructor( private val fragmentService: FragmentService, private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy> private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>, private val qsFragmentComposeProvider: Provider<QSFragmentCompose>, ) : CoreStartable { override fun start() { fragmentService.addFragmentInstantiationProvider( QSFragmentLegacy::class.java, qsFragmentLegacyProvider ) fragmentService.addFragmentInstantiationProvider( QSFragmentCompose::class.java, qsFragmentComposeProvider ) } } Loading