Loading packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/ui/compose/LoadIconTest.kt 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.screencapture.common.ui.compose import android.content.testableContext import android.graphics.drawable.TestStubDrawable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.screencapture.common.ui.viewmodel.drawableLoaderViewModel import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LoadIconTest : SysuiTestCase() { @get:Rule val composeTestRule = createComposeRule() private val kosmos = testKosmosNew() private val testDrawable1 = TestStubDrawable("1") private val testDrawable2 = TestStubDrawable("2") @Before fun setUp() { overrideResource(1, testDrawable1) overrideResource(2, testDrawable2) } @Test fun testLoadingIcon() = with(kosmos) { val loadedIcons = mutableListOf<Icon.Loaded?>() composeTestRule.setContent { CompositionLocalProvider(LocalContext provides testableContext) { for (resId in listOf(1, 2)) { val icon by loadIcon( viewModel = drawableLoaderViewModel, resId = resId, contentDescription = null, ) SideEffect { loadedIcons.add(icon) } } } } composeTestRule.waitForIdle() assertThat(loadedIcons.size).isEqualTo(2) assertThat(loadedIcons[0]!!.res).isEqualTo(1) assertThat(loadedIcons[0]!!.drawable).isEqualTo(testDrawable1) assertThat(loadedIcons[1]!!.res).isEqualTo(2) assertThat(loadedIcons[1]!!.drawable).isEqualTo(testDrawable2) } } packages/SystemUI/src/com/android/systemui/screencapture/common/ui/compose/LoadDrawable.kt +5 −7 Original line number Diff line number Diff line Loading @@ -19,18 +19,16 @@ package com.android.systemui.screencapture.common.ui.compose import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel /** @see DrawableLoaderViewModel */ @Composable fun loadDrawable(viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int): Drawable? { fun loadDrawable(viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int): State<Drawable?> { val context = LocalContext.current val drawable by produceState<Drawable?>(initialValue = null, keys = arrayOf(viewModel, context, resId)) { return produceState<Drawable?>(initialValue = null, keys = arrayOf(viewModel, context, resId)) { value = viewModel.loadDrawable(context = context, resId = resId) } return drawable } packages/SystemUI/src/com/android/systemui/screencapture/common/ui/compose/LoadIcon.kt +12 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.systemui.screencapture.common.ui.compose import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel Loading @@ -28,8 +31,14 @@ fun loadIcon( viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int, contentDescription: ContentDescription?, ): Icon.Loaded? { return loadDrawable(viewModel, resId)?.let { drawable -> ): State<Icon.Loaded?> { val context = LocalContext.current return produceState<Icon.Loaded?>( initialValue = null, keys = arrayOf(viewModel, context, resId), ) { val drawable = viewModel.loadDrawable(context = context, resId = resId) value = Icon.Loaded(drawable = drawable, res = resId, contentDescription = contentDescription) } } packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +13 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource Loading Loading @@ -70,7 +71,8 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { viewModel = viewModel, resId = R.drawable.ic_screen_capture_camera, contentDescription = null, ), ) .value, text = stringResource(R.string.screen_capture_fullscreen_screenshot_button), onClick = { viewModel.takeFullscreenScreenshot() }, ) Loading @@ -80,15 +82,16 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { ScreenCaptureRegion.PARTIAL -> { // TODO(b/427541309) Set the initial width and height of the RegionBox based on the // viewmodel state. RegionBox( buttonText = stringResource(id = R.string.screen_capture_region_selection_button), buttonIcon = val icon by loadIcon( viewModel = viewModel, resId = R.drawable.ic_screen_capture_camera, contentDescription = null, ), ) RegionBox( buttonText = stringResource(id = R.string.screen_capture_region_selection_button), buttonIcon = icon, onRegionSelected = { rect: Rect -> viewModel.updateRegionBox(rect) }, onCaptureClick = { viewModel.takePartialScreenshot() }, ) Loading packages/SystemUI/src/com/android/systemui/screencapture/record/smallscreen/ui/SmallScreenPostRecordingActivity.kt +10 −9 Original line number Diff line number Diff line Loading @@ -149,14 +149,15 @@ constructor(private val viewModelFactory: PostRecordingViewModel.Factory) : Comp } } if (!shouldUseFlatBottomBar) { PrimaryButton( text = stringResource(R.string.screenrecord_share_label), icon = val shareIcon by loadIcon( viewModel, R.drawable.ic_screenshot_share, contentDescription = null, ), ) PrimaryButton( text = stringResource(R.string.screenrecord_share_label), icon = shareIcon, onClick = { viewModel.share() }, modifier = Modifier.fillMaxWidth() Loading @@ -170,7 +171,7 @@ constructor(private val viewModelFactory: PostRecordingViewModel.Factory) : Comp modifier = Modifier.padding(horizontal = 12.dp).size(48.dp).align(Alignment.TopEnd), ) { LoadingIcon( icon = loadIcon(viewModel, R.drawable.ic_close, null), icon = loadIcon(viewModel, R.drawable.ic_close, null).value, tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(24.dp), ) Loading Loading @@ -214,7 +215,7 @@ private fun PostRecordSnackbar( .size(24.dp), ) { LoadingIcon( icon = loadIcon(viewModel, visuals.iconRes, null), icon = loadIcon(viewModel, visuals.iconRes, null).value, tint = MaterialTheme.colorScheme.inverseSurface, modifier = modifier.size(16.dp), ) Loading Loading @@ -252,7 +253,7 @@ private fun PostRecordButton( border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant), ) { LoadingIcon( icon = loadIcon(drawableLoaderViewModel, iconRes, contentDescription = null), icon = loadIcon(drawableLoaderViewModel, iconRes, contentDescription = null).value, modifier = Modifier.size(20.dp), ) Spacer(modifier = Modifier.width(8.dp)) Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/ui/compose/LoadIconTest.kt 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.screencapture.common.ui.compose import android.content.testableContext import android.graphics.drawable.TestStubDrawable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.screencapture.common.ui.viewmodel.drawableLoaderViewModel import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LoadIconTest : SysuiTestCase() { @get:Rule val composeTestRule = createComposeRule() private val kosmos = testKosmosNew() private val testDrawable1 = TestStubDrawable("1") private val testDrawable2 = TestStubDrawable("2") @Before fun setUp() { overrideResource(1, testDrawable1) overrideResource(2, testDrawable2) } @Test fun testLoadingIcon() = with(kosmos) { val loadedIcons = mutableListOf<Icon.Loaded?>() composeTestRule.setContent { CompositionLocalProvider(LocalContext provides testableContext) { for (resId in listOf(1, 2)) { val icon by loadIcon( viewModel = drawableLoaderViewModel, resId = resId, contentDescription = null, ) SideEffect { loadedIcons.add(icon) } } } } composeTestRule.waitForIdle() assertThat(loadedIcons.size).isEqualTo(2) assertThat(loadedIcons[0]!!.res).isEqualTo(1) assertThat(loadedIcons[0]!!.drawable).isEqualTo(testDrawable1) assertThat(loadedIcons[1]!!.res).isEqualTo(2) assertThat(loadedIcons[1]!!.drawable).isEqualTo(testDrawable2) } }
packages/SystemUI/src/com/android/systemui/screencapture/common/ui/compose/LoadDrawable.kt +5 −7 Original line number Diff line number Diff line Loading @@ -19,18 +19,16 @@ package com.android.systemui.screencapture.common.ui.compose import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel /** @see DrawableLoaderViewModel */ @Composable fun loadDrawable(viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int): Drawable? { fun loadDrawable(viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int): State<Drawable?> { val context = LocalContext.current val drawable by produceState<Drawable?>(initialValue = null, keys = arrayOf(viewModel, context, resId)) { return produceState<Drawable?>(initialValue = null, keys = arrayOf(viewModel, context, resId)) { value = viewModel.loadDrawable(context = context, resId = resId) } return drawable }
packages/SystemUI/src/com/android/systemui/screencapture/common/ui/compose/LoadIcon.kt +12 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.systemui.screencapture.common.ui.compose import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel Loading @@ -28,8 +31,14 @@ fun loadIcon( viewModel: DrawableLoaderViewModel, @DrawableRes resId: Int, contentDescription: ContentDescription?, ): Icon.Loaded? { return loadDrawable(viewModel, resId)?.let { drawable -> ): State<Icon.Loaded?> { val context = LocalContext.current return produceState<Icon.Loaded?>( initialValue = null, keys = arrayOf(viewModel, context, resId), ) { val drawable = viewModel.loadDrawable(context = context, resId = resId) value = Icon.Loaded(drawable = drawable, res = resId, contentDescription = contentDescription) } }
packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +13 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource Loading Loading @@ -70,7 +71,8 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { viewModel = viewModel, resId = R.drawable.ic_screen_capture_camera, contentDescription = null, ), ) .value, text = stringResource(R.string.screen_capture_fullscreen_screenshot_button), onClick = { viewModel.takeFullscreenScreenshot() }, ) Loading @@ -80,15 +82,16 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { ScreenCaptureRegion.PARTIAL -> { // TODO(b/427541309) Set the initial width and height of the RegionBox based on the // viewmodel state. RegionBox( buttonText = stringResource(id = R.string.screen_capture_region_selection_button), buttonIcon = val icon by loadIcon( viewModel = viewModel, resId = R.drawable.ic_screen_capture_camera, contentDescription = null, ), ) RegionBox( buttonText = stringResource(id = R.string.screen_capture_region_selection_button), buttonIcon = icon, onRegionSelected = { rect: Rect -> viewModel.updateRegionBox(rect) }, onCaptureClick = { viewModel.takePartialScreenshot() }, ) Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/smallscreen/ui/SmallScreenPostRecordingActivity.kt +10 −9 Original line number Diff line number Diff line Loading @@ -149,14 +149,15 @@ constructor(private val viewModelFactory: PostRecordingViewModel.Factory) : Comp } } if (!shouldUseFlatBottomBar) { PrimaryButton( text = stringResource(R.string.screenrecord_share_label), icon = val shareIcon by loadIcon( viewModel, R.drawable.ic_screenshot_share, contentDescription = null, ), ) PrimaryButton( text = stringResource(R.string.screenrecord_share_label), icon = shareIcon, onClick = { viewModel.share() }, modifier = Modifier.fillMaxWidth() Loading @@ -170,7 +171,7 @@ constructor(private val viewModelFactory: PostRecordingViewModel.Factory) : Comp modifier = Modifier.padding(horizontal = 12.dp).size(48.dp).align(Alignment.TopEnd), ) { LoadingIcon( icon = loadIcon(viewModel, R.drawable.ic_close, null), icon = loadIcon(viewModel, R.drawable.ic_close, null).value, tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(24.dp), ) Loading Loading @@ -214,7 +215,7 @@ private fun PostRecordSnackbar( .size(24.dp), ) { LoadingIcon( icon = loadIcon(viewModel, visuals.iconRes, null), icon = loadIcon(viewModel, visuals.iconRes, null).value, tint = MaterialTheme.colorScheme.inverseSurface, modifier = modifier.size(16.dp), ) Loading Loading @@ -252,7 +253,7 @@ private fun PostRecordButton( border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant), ) { LoadingIcon( icon = loadIcon(drawableLoaderViewModel, iconRes, contentDescription = null), icon = loadIcon(drawableLoaderViewModel, iconRes, contentDescription = null).value, modifier = Modifier.size(20.dp), ) Spacer(modifier = Modifier.width(8.dp)) Loading