Merge "Motion for hub <-> edit mode transition" into main
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index b1258ba..c4659cf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -55,6 +55,7 @@
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
@@ -76,6 +77,15 @@
override fun matches(key: ElementKey, scene: SceneKey) = true
}
+private object TransitionDuration {
+ const val BETWEEN_HUB_AND_EDIT_MODE_MS = 1000
+ const val EDIT_MODE_TO_HUB_CONTENT_MS = 167
+ const val EDIT_MODE_TO_HUB_GRID_DELAY_MS = 167
+ const val EDIT_MODE_TO_HUB_GRID_END_MS =
+ EDIT_MODE_TO_HUB_GRID_DELAY_MS + EDIT_MODE_TO_HUB_CONTENT_MS
+ const val HUB_TO_EDIT_MODE_CONTENT_MS = 250
+}
+
val sceneTransitions = transitions {
to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) {
spec = tween(durationMillis = 250)
@@ -97,6 +107,30 @@
}
timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
+ to(CommunalScenes.Blank, key = CommunalTransitionKeys.ToEditMode) {
+ spec = tween(durationMillis = TransitionDuration.BETWEEN_HUB_AND_EDIT_MODE_MS)
+ timestampRange(endMillis = TransitionDuration.HUB_TO_EDIT_MODE_CONTENT_MS) {
+ fade(Communal.Elements.Grid)
+ fade(Communal.Elements.IndicationArea)
+ fade(Communal.Elements.LockIcon)
+ }
+ fade(Communal.Elements.Scrim)
+ }
+ to(CommunalScenes.Communal, key = CommunalTransitionKeys.FromEditMode) {
+ spec = tween(durationMillis = TransitionDuration.BETWEEN_HUB_AND_EDIT_MODE_MS)
+ translate(Communal.Elements.Grid, y = SlideOffsetY)
+ timestampRange(endMillis = TransitionDuration.EDIT_MODE_TO_HUB_CONTENT_MS) {
+ fade(Communal.Elements.IndicationArea)
+ fade(Communal.Elements.LockIcon)
+ fade(Communal.Elements.Scrim)
+ }
+ timestampRange(
+ startMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_DELAY_MS,
+ endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS
+ ) {
+ fade(Communal.Elements.Grid)
+ }
+ }
}
/**
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 f43064a..a1899fd 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
@@ -33,6 +33,8 @@
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
@@ -128,6 +130,7 @@
import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
+import com.android.compose.animation.Easings.Emphasized
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -176,6 +179,10 @@
derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false)
+ val isCommunalContentVisible by
+ viewModel.isCommunalContentVisible.collectAsStateWithLifecycle(
+ initialValue = !viewModel.isEditMode
+ )
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -248,47 +255,88 @@
viewModel = viewModel,
)
} else {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isPointerWithinEnabledRemoveButton(
- removeEnabled = removeButtonEnabled,
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
+ val slideOffsetInPx =
+ with(LocalDensity.current) { Dimensions.SlideOffsetY.toPx().toInt() }
+ AnimatedVisibility(
+ visible = isCommunalContentVisible,
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(durationMillis = 83, delayMillis = 83, easing = LinearEasing)
+ ) +
+ slideInVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ initialOffsetY = { -slideOffsetInPx }
+ ),
+ exit =
+ fadeOut(
+ animationSpec = tween(durationMillis = 167, easing = LinearEasing)
+ ) +
+ slideOutVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ targetOffsetY = { -slideOffsetInPx }
+ ),
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Box {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isPointerWithinEnabledRemoveButton(
+ removeEnabled = removeButtonEnabled,
+ offset =
+ gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ },
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ interactionHandler = interactionHandler,
)
- },
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- interactionHandler = interactionHandler,
- )
+ }
+ }
}
}
- if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
- Toolbar(
- setToolbarSize = { toolbarSize = it },
- setRemoveButtonCoordinates = { removeButtonCoordinates = it },
- onEditDone = onEditDone,
- onOpenWidgetPicker = onOpenWidgetPicker,
- onRemoveClicked = {
- val index =
- selectedKey.value?.let { key ->
- contentListState.list.indexOfFirst { it.key == key }
+ if (onOpenWidgetPicker != null && onEditDone != null) {
+ AnimatedVisibility(
+ visible = viewModel.isEditMode && isCommunalContentVisible,
+ enter =
+ fadeIn(animationSpec = tween(durationMillis = 250, easing = LinearEasing)) +
+ slideInVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ ),
+ exit =
+ fadeOut(animationSpec = tween(durationMillis = 167, easing = LinearEasing)) +
+ slideOutVertically(
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized)
+ ),
+ ) {
+ Toolbar(
+ setToolbarSize = { toolbarSize = it },
+ setRemoveButtonCoordinates = { removeButtonCoordinates = it },
+ onEditDone = onEditDone,
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ onRemoveClicked = {
+ val index =
+ selectedKey.value?.let { key ->
+ contentListState.list.indexOfFirst { it.key == key }
+ }
+ index?.let {
+ contentListState.onRemove(it)
+ contentListState.onSaveList()
+ viewModel.setSelectedKey(null)
}
- index?.let {
- contentListState.onRemove(it)
- contentListState.onSaveList()
- viewModel.setSelectedKey(null)
- }
- },
- removeEnabled = removeButtonEnabled
- )
+ },
+ removeEnabled = removeButtonEnabled
+ )
+ }
}
if (currentPopup == PopupType.CtaTile) {
PopupOnDismissCtaTile(viewModel::onHidePopup)
@@ -1331,6 +1379,7 @@
horizontal = ToolbarButtonPaddingHorizontal,
)
val IconSize = 40.dp
+ val SlideOffsetY = 30.dp
}
private object Colors {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index e61b2d0..cf14547 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -111,6 +111,25 @@
}
}
+ @Test
+ fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalInteractor.setEditModeOpen(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+ // Scene change will be handled in EditWidgetsActivity not here
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
@Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun deviceDocked_forceCommunalScene() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e42a67b..3d454a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -121,6 +122,7 @@
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var userTracker: FakeUserTracker
private lateinit var activityStarter: ActivityStarter
private lateinit var userManager: UserManager
@@ -141,6 +143,7 @@
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
userTracker = kosmos.fakeUserTracker
activityStarter = kosmos.activityStarter
userManager = kosmos.userManager
@@ -815,7 +818,11 @@
@Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
+ val editModeState by collectLastValue(communalSceneInteractor.editModeState)
+
underTest.showWidgetEditor()
+
+ assertThat(editModeState).isEqualTo(EditModeState.STARTING)
verify(editWidgetsActivityStarter).startActivity()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index a0e7781..6e48b99 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
@@ -82,6 +83,27 @@
}
@Test
+ fun snapToSceneForActivity() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+ }
+
+ @Test
+ fun snapToSceneForActivity_willNotChangeScene_forEditModeActivity() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.setEditModeState(EditModeState.STARTING)
+ underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @Test
fun transitionProgress_fullProgress() =
testScope.runTest {
val transitionProgress by
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
index 84dbfd4..d5fe2a1 100644
--- 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
@@ -38,14 +38,17 @@
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
@@ -86,6 +89,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private val testableResources = context.orCreateTestableResources
@@ -99,6 +103,7 @@
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
+ communalSceneInteractor = kosmos.communalSceneInteractor
kosmos.fakeUserTracker.set(
userInfos = listOf(MAIN_USER_INFO),
selectedUserIndex = 0,
@@ -107,9 +112,10 @@
underTest =
CommunalEditModeViewModel(
- kosmos.communalSceneInteractor,
+ communalSceneInteractor,
kosmos.communalInteractor,
kosmos.communalSettingsInteractor,
+ kosmos.keyguardTransitionInteractor,
mediaHost,
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
@@ -172,6 +178,22 @@
}
@Test
+ fun isCommunalContentVisible_isTrue_whenEditModeShowing() =
+ testScope.runTest {
+ val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
+ communalSceneInteractor.setEditModeState(EditModeState.SHOWING)
+ assertThat(isCommunalContentVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun isCommunalContentVisible_isFalse_whenEditModeNotShowing() =
+ testScope.runTest {
+ val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
+ communalSceneInteractor.setEditModeState(null)
+ assertThat(isCommunalContentVisible).isEqualTo(false)
+ }
+
+ @Test
fun deleteWidget() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index a12b6f8..d20fec4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -166,4 +167,37 @@
to = KeyguardState.OCCLUDED
)
}
+
+ @Test
+ fun transitionToGone_whenOpeningGlanceableHubEditMode() =
+ testScope.runTest {
+ kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
+ runCurrent()
+
+ // On Glanceable hub and edit mode activity is started
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope
+ )
+ reset(transitionRepository)
+
+ kosmos.communalInteractor.setEditModeOpen(true)
+ runCurrent()
+
+ // Auth and alternate bouncer is hidden
+ kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(false)
+ advanceTimeBy(200) // advance past delay
+
+ // Then no transition should occur yet
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ // When keyguard is going away
+ kosmos.fakeKeyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ // Then transition to GONE should occur
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d522c7e..88c3f9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -208,7 +208,10 @@
// doing any translation.
CommunalScenes.Communal
}
- to == KeyguardState.GONE -> CommunalScenes.Blank
+ // Transitioning to Blank scene when entering the edit mode will be handled separately
+ // with custom animations.
+ to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
+ CommunalScenes.Blank
!docked && !KeyguardState.deviceIsAwakeInState(to) -> {
// If the user taps the screen and wakes the device within this timeout, we don't
// want to dismiss the hub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 3e513f8..00678a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -38,6 +38,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -307,6 +308,7 @@
preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {
+ communalSceneInteractor.setEditModeState(EditModeState.STARTING)
editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 0dab67c..20d8a2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,14 +22,17 @@
import com.android.systemui.communal.data.repository.CommunalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -57,11 +60,32 @@
communalSceneRepository.snapToScene(newScene, delayMillis)
}
+ /** Immediately snaps to the new scene when activity is started. */
+ fun snapToSceneForActivityStart(newScene: SceneKey, delayMillis: Long = 0) {
+ // skip if we're starting edit mode activity, as it will be handled later by changeScene
+ // with transition key [CommunalTransitionKeys.ToEditMode].
+ if (_editModeState.value == EditModeState.STARTING) {
+ return
+ }
+ snapToScene(newScene, delayMillis)
+ }
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*/
val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+ private val _editModeState = MutableStateFlow<EditModeState?>(null)
+ /**
+ * Current state for glanceable hub edit mode, used to chain the animations when transitioning
+ * between communal scene and the edit mode activity.
+ */
+ val editModeState = _editModeState.asStateFlow()
+
+ fun setEditModeState(value: EditModeState?) {
+ _editModeState.value = value
+ }
+
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> =
communalSceneRepository.transitionState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index 73cfb52..11fb233 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -26,6 +26,10 @@
object CommunalTransitionKeys {
/** Fades the glanceable hub without any translation */
val SimpleFade = TransitionKey("SimpleFade")
+ /** Transition from the glanceable hub before entering edit mode */
+ val ToEditMode = TransitionKey("ToEditMode")
+ /** Transition to the glanceable hub after exiting edit mode */
+ val FromEditMode = TransitionKey("FromEditMode")
/** Immediately transitions without any delay */
val Immediately = TransitionKey("Immediately")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt
new file mode 100644
index 0000000..ace9c2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/EditModeState.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.communal.shared.model
+
+/**
+ * Models the state of the edit mode activity. Used to chain the animation during the transition
+ * between the hub on communal scene, and the edit mode activity after unlocking the keyguard.
+ */
+enum class EditModeState(val value: Int) {
+ // starting activity after dismissing keyguard
+ STARTING(0),
+ // activity content is showing
+ SHOWING(1),
+}
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 8cd5603..cc90730 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
@@ -25,6 +25,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.media.controls.ui.view.MediaHost
import kotlinx.coroutines.flow.Flow
@@ -34,12 +35,15 @@
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
- private val communalSceneInteractor: CommunalSceneInteractor,
+ val communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
+ /** Used to animate showing or hiding the communal content. */
+ open val isCommunalContentVisible: Flow<Boolean> = MutableStateFlow(false)
+
/** Whether communal hub should be focused by accessibility tools. */
open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
@@ -63,6 +67,8 @@
communalSceneInteractor.changeScene(scene, transitionKey)
}
+ fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
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
index 5312aec..c0c5861 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -30,21 +30,27 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -56,6 +62,7 @@
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
@@ -66,6 +73,20 @@
override val isEditMode = true
+ override val isCommunalContentVisible: Flow<Boolean> =
+ communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING }
+
+ /**
+ * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE]
+ * and edit mode is open.
+ */
+ val canShowEditMode =
+ allOf(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+ communalInteractor.editModeOpen
+ )
+ .filter { it }
+
// Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
@@ -172,6 +193,11 @@
/** Sets whether edit mode is currently open */
fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
+ /** Called when exiting the edit mode, before transitioning back to the communal scene. */
+ fun cleanupEditModeState() {
+ communalSceneInteractor.setEditModeState(null)
+ }
+
companion object {
private const val TAG = "CommunalEditModeViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index c6fa5a84..11247df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -125,6 +125,8 @@
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ override val isCommunalContentVisible: Flow<Boolean> = MutableStateFlow(true)
+
/**
* Freeze the content flow, when an activity is about to show, like starting a timer via voice:
* 1) in handheld mode, use the keyguard occluded state;
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 50477b1..40df6cec 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -37,6 +37,7 @@
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
@@ -107,6 +108,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ listenForTransitionAndChangeScene()
communalViewModel.setEditModeOpen(true)
@@ -138,6 +140,22 @@
}
}
+ // Handle scene change to show the activity and animate in its content
+ private fun listenForTransitionAndChangeScene() {
+ lifecycleScope.launch {
+ communalViewModel.canShowEditMode.collect {
+ communalViewModel.changeScene(
+ CommunalScenes.Blank,
+ CommunalTransitionKeys.ToEditMode
+ )
+ // wait till transitioned to Blank scene, then animate in communal content in
+ // edit mode
+ communalViewModel.currentScene.first { it == CommunalScenes.Blank }
+ communalViewModel.setEditModeState(EditModeState.SHOWING)
+ }
+ }
+ }
+
private fun onOpenWidgetPicker() {
lifecycleScope.launch {
communalViewModel.onOpenWidgetPicker(
@@ -150,9 +168,11 @@
private fun onEditDone() {
lifecycleScope.launch {
+ communalViewModel.cleanupEditModeState()
+
communalViewModel.changeScene(
CommunalScenes.Communal,
- CommunalTransitionKeys.SimpleFade
+ CommunalTransitionKeys.FromEditMode
)
// Wait for the current scene to be idle on communal.
@@ -192,6 +212,7 @@
override fun onDestroy() {
super.onDestroy()
+ communalViewModel.cleanupEditModeState()
communalViewModel.setEditModeOpen(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 14eb972..49d00af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -114,13 +114,26 @@
powerInteractor.isAwake,
keyguardInteractor.isAodAvailable,
communalInteractor.isIdleOnCommunal,
+ communalInteractor.editModeOpen,
keyguardInteractor.isKeyguardOccluded,
)
.filterRelevantKeyguardStateAnd {
(isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _) ->
!isAlternateBouncerShowing && !isPrimaryBouncerShowing
}
- .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isOccluded) ->
+ .collect {
+ (
+ _,
+ _,
+ isAwake,
+ isAodAvailable,
+ isIdleOnCommunal,
+ isCommunalEditMode,
+ isOccluded) ->
+ // When unlocking over glanceable hub to enter edit mode, transitioning directly
+ // to GONE prevents the lockscreen flash. Let listenForAlternateBouncerToGone
+ // handle it.
+ if (isCommunalEditMode) return@collect
val to =
if (!isAwake) {
if (isAodAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e5fc4e2..800d7fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -469,7 +469,7 @@
shadeControllerLazy.get().collapseShadeForActivityStart()
}
if (communalHub()) {
- communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.snapToSceneForActivityStart(CommunalScenes.Blank)
}
return deferred
}
@@ -556,7 +556,7 @@
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationStart(isExpandingFullyAbove)
if (communalHub()) {
- communalSceneInteractor.snapToScene(
+ communalSceneInteractor.snapToSceneForActivityStart(
CommunalScenes.Blank,
ActivityTransitionAnimator.TIMINGS.totalDuration
)