diff options
11 files changed, 245 insertions, 57 deletions
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index e369773f37f1..914e5f2c17bf 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -47,6 +47,14 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) { + throwComposeUnavailableError() + } + override fun createFooterActionsView( context: Context, viewModel: FooterActionsViewModel, diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 7aba4308053b..59bd95bd9027 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -62,6 +62,21 @@ object ComposeFacade : BaseComposeFacade { activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } } } + override fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) { + activity.setContent { + PlatformTheme { + CommunalHub( + viewModel = viewModel, + onOpenWidgetPicker = onOpenWidgetPicker, + ) + } + } + } + override fun createFooterActionsView( context: Context, viewModel: FooterActionsViewModel, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 4891a680e2e1..6161cc12d2ac 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -34,6 +34,7 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -60,6 +61,7 @@ import com.android.systemui.res.R fun CommunalHub( modifier: Modifier = Modifier, viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: (() -> Unit)? = null, ) { val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) Box( @@ -81,7 +83,7 @@ fun CommunalHub( modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth), model = communalContent[index], viewModel = viewModel, - deleteOnClick = viewModel::onDeleteWidget, + deleteOnClick = if (viewModel.isEditMode) viewModel::onDeleteWidget else null, size = SizeF( Dimensions.CardWidth.value, @@ -90,8 +92,14 @@ fun CommunalHub( ) } } - IconButton(onClick = viewModel::onOpenWidgetEditor) { - Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor)) + if (viewModel.isEditMode && onOpenWidgetPicker != null) { + IconButton(onClick = onOpenWidgetPicker) { + Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + } + } else { + IconButton(onClick = viewModel::onOpenWidgetEditor) { + Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor)) + } } // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving @@ -111,7 +119,7 @@ private fun CommunalContent( model: CommunalContentModel, viewModel: BaseCommunalViewModel, size: SizeF, - deleteOnClick: (id: Int) -> Unit, + deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { when (model) { @@ -126,19 +134,22 @@ private fun CommunalContent( private fun WidgetContent( model: CommunalContentModel.Widget, size: SizeF, - deleteOnClick: (id: Int) -> Unit, + deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { // TODO(b/309009246): update background color Box( modifier = modifier.fillMaxSize().background(Color.White), ) { - IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { - Icon( - Icons.Default.Close, - LocalContext.current.getString(R.string.button_to_remove_widget) - ) + if (deleteOnClick != null) { + IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { + Icon( + Icons.Default.Close, + LocalContext.current.getString(R.string.button_to_remove_widget) + ) + } } + AndroidView( modifier = modifier, factory = { context -> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt new file mode 100644 index 000000000000..ce7db80db7da --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 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.communal.view.viewmodel + +import android.app.smartspace.SmartspaceTarget +import android.provider.Settings +import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalEditModeViewModelTest : SysuiTestCase() { + @Mock private lateinit var mediaHost: MediaHost + + private lateinit var testScope: TestScope + + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var tutorialRepository: FakeCommunalTutorialRepository + private lateinit var widgetRepository: FakeCommunalWidgetRepository + private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var mediaRepository: FakeCommunalMediaRepository + + private lateinit var underTest: CommunalEditModeViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + + val withDeps = CommunalInteractorFactory.create() + keyguardRepository = withDeps.keyguardRepository + communalRepository = withDeps.communalRepository + tutorialRepository = withDeps.tutorialRepository + widgetRepository = withDeps.widgetRepository + smartspaceRepository = withDeps.smartspaceRepository + mediaRepository = withDeps.mediaRepository + + underTest = + CommunalEditModeViewModel( + withDeps.communalInteractor, + mediaHost, + ) + } + + @Test + fun communalContent_onlyWidgetsAreShownInEditMode() = + testScope.runTest { + tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + + // Widgets available. + val widgets = + listOf( + CommunalWidgetContentModel( + appWidgetId = 0, + priority = 30, + providerInfo = mock(), + ), + CommunalWidgetContentModel( + appWidgetId = 1, + priority = 20, + providerInfo = mock(), + ), + ) + widgetRepository.setCommunalWidgets(widgets) + + // Smartspace available. + val target = Mockito.mock(SmartspaceTarget::class.java) + whenever(target.smartspaceTargetId).thenReturn("target") + whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) + whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) + smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + + // Media playing. + mediaRepository.mediaPlaying.value = true + + val communalContent by collectLastValue(underTest.communalContent) + + // Only Widgets are shown. + assertThat(communalContent?.size).isEqualTo(2) + assertThat(communalContent?.get(0)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + assertThat(communalContent?.get(1)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + } +} diff --git a/packages/SystemUI/res/layout/edit_widgets.xml b/packages/SystemUI/res/layout/edit_widgets.xml deleted file mode 100644 index 182e651aa66d..000000000000 --- a/packages/SystemUI/res/layout/edit_widgets.xml +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - ~ Copyright (C) 2023 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. - --> - -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/edit_widgets" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <Button - style="@android:Widget.DeviceDefault.Button.Colored" - android:id="@+id/add_widget" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:textSize="28sp" - android:text="@string/hub_mode_add_widget_button_text"/> - -</FrameLayout> diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 10e9ee855be0..98f3594801f3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -37,6 +37,9 @@ abstract class BaseCommunalViewModel( /** A list of all the communal content to be displayed in the communal hub. */ abstract val communalContent: Flow<List<CommunalContentModel>> + /** Whether in edit mode for the communal hub. */ + open val isEditMode = false + /** Called as the UI requests deleting a widget. */ open fun onDeleteWidget(id: Int) {} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt new file mode 100644 index 000000000000..14d9b2ca80f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.media.dagger.MediaModule +import javax.inject.Inject +import javax.inject.Named +import kotlinx.coroutines.flow.Flow + +/** The view model for communal hub in edit mode. */ +@SysUISingleton +class CommunalEditModeViewModel +@Inject +constructor( + private val communalInteractor: CommunalInteractor, + @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, +) : BaseCommunalViewModel(communalInteractor, mediaHost) { + + override val isEditMode = true + + // Only widgets are editable. + override val communalContent: Flow<List<CommunalContentModel>> = + communalInteractor.widgetContent + + override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 78e85db9ea05..7b94fc182fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -20,17 +20,21 @@ import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.os.Bundle import android.util.Log -import android.view.View import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.res.R +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent import javax.inject.Inject /** An Activity for editing the widgets that appear in hub mode. */ -class EditWidgetsActivity @Inject constructor(private val communalInteractor: CommunalInteractor) : - ComponentActivity() { +class EditWidgetsActivity +@Inject +constructor( + private val communalViewModel: CommunalEditModeViewModel, + private val communalInteractor: CommunalInteractor, +) : ComponentActivity() { companion object { /** * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode. @@ -59,20 +63,19 @@ class EditWidgetsActivity @Inject constructor(private val communalInteractor: Co "Failed to receive result from widget picker, code=${result.resultCode}" ) } - this@EditWidgetsActivity.finish() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setShowWhenLocked(true) - setContentView(R.layout.edit_widgets) - - val addWidgetsButton = findViewById<View>(R.id.add_widget) - addWidgetsButton?.setOnClickListener({ - addWidgetActivityLauncher.launch( - Intent(applicationContext, WidgetPickerActivity::class.java) - ) - }) + setCommunalEditWidgetActivityContent( + activity = this, + viewModel = communalViewModel, + onOpenWidgetPicker = { + addWidgetActivityLauncher.launch( + Intent(applicationContext, WidgetPickerActivity::class.java) + ) + }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt index 3e6dbd5a7115..a2765486bf2d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt @@ -43,7 +43,6 @@ constructor( super.onCreate(savedInstanceState) setContentView(R.layout.widget_picker) - setShowWhenLocked(true) loadWidgets() } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 38c120da614b..65d44957222a 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -58,6 +58,13 @@ interface BaseComposeFacade { onResult: (PeopleViewModel.Result) -> Unit, ) + /** Bind the content of [activity] to [viewModel]. */ + fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) + /** Create a [View] to represent [viewModel] on screen. */ fun createFooterActionsView( context: Context, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index d3744d55c55d..4068e408f0bd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -167,6 +167,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _isKeyguardOccluded.value = isOccluded } + fun setKeyguardUnlocked(isUnlocked: Boolean) { + _isKeyguardUnlocked.value = isUnlocked + } + override fun setIsDozing(isDozing: Boolean) { _isDozing.value = isDozing } |