diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt index 18e8a962dc70e..bf922bc98bcb8 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt @@ -21,4 +21,5 @@ object KeyguardQuickAffordancePreviewConstants { const val MESSAGE_ID_SLOT_SELECTED = 1337 const val KEY_SLOT_ID = "slot_id" const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id" + const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index d020529d2baec..e9d7a5b8ed6f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -66,6 +66,8 @@ import kotlinx.coroutines.launch object KeyguardBottomAreaViewBinder { private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L + private const val SCALE_SELECTED_BUTTON = 1.23f + private const val DIM_ALPHA = 0.3f /** * Defines interface for an object that acts as the binding between the view and its view-model. @@ -315,6 +317,12 @@ object KeyguardBottomAreaViewBinder { } else { null } + view + .animate() + .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) + .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) + .alpha(if (viewModel.isDimmed) DIM_ALPHA else 1f) + .start() view.isClickable = viewModel.isClickable if (viewModel.isClickable) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a5ae8ba58d459..88085749ece08 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -24,7 +24,6 @@ import android.content.IntentFilter import android.hardware.display.DisplayManager import android.os.Bundle import android.os.IBinder -import android.view.Gravity import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View @@ -65,6 +64,11 @@ constructor( val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT) + private val shouldHighlightSelectedAffordance: Boolean = + bundle.getBoolean( + KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, + false, + ) private var host: SurfaceControlViewHost @@ -82,6 +86,7 @@ constructor( bundle.getString( KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, ), + shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) runBlocking(mainDispatcher) { host = @@ -154,8 +159,7 @@ constructor( bottomAreaView, FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.BOTTOM, + FrameLayout.LayoutParams.MATCH_PARENT, ), ) } @@ -195,7 +199,13 @@ constructor( ?.events ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) clockView?.let { parentView.removeView(it) } - clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) } + clockView = + clockController.clock?.largeClock?.view?.apply { + if (shouldHighlightSelectedAffordance) { + alpha = DIM_ALPHA + } + parentView.addView(this) + } } companion object { @@ -203,5 +213,7 @@ constructor( private const val KEY_VIEW_WIDTH = "width" private const val KEY_VIEW_HEIGHT = "height" private const val KEY_DISPLAY_ID = "display_id" + + private const val DIM_ALPHA = 0.3f } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 5d85680efcf46..1e3b60c27d841 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -45,12 +45,17 @@ constructor( private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, ) { + data class PreviewMode( + val isInPreviewMode: Boolean = false, + val shouldHighlightSelectedAffordance: Boolean = false, + ) + /** * Whether this view-model instance is powering the preview experience that renders exclusively * in the wallpaper picker application. This should _always_ be `false` for the real lock screen * experience. */ - private val isInPreviewMode = MutableStateFlow(false) + private val previewMode = MutableStateFlow(PreviewMode()) /** * ID of the slot that's currently selected in the preview that renders exclusively in the @@ -87,8 +92,8 @@ constructor( keyguardInteractor.isDozing.map { !it }.distinctUntilChanged() /** An observable for the alpha level for the entire bottom area. */ val alpha: Flow = - isInPreviewMode.flatMapLatest { isInPreviewMode -> - if (isInPreviewMode) { + previewMode.flatMapLatest { + if (it.isInPreviewMode) { flowOf(1f) } else { bottomAreaInteractor.alpha.distinctUntilChanged() @@ -129,9 +134,18 @@ constructor( * lock screen. * * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one. + * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be + * highlighted (while all others are dimmed to make the selected one stand out). */ - fun enablePreviewMode(initiallySelectedSlotId: String?) { - isInPreviewMode.value = true + fun enablePreviewMode( + initiallySelectedSlotId: String?, + shouldHighlightSelectedAffordance: Boolean, + ) { + previewMode.value = + PreviewMode( + isInPreviewMode = true, + shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, + ) onPreviewSlotSelected( initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START ) @@ -150,9 +164,9 @@ constructor( private fun button( position: KeyguardQuickAffordancePosition ): Flow { - return isInPreviewMode.flatMapLatest { isInPreviewMode -> + return previewMode.flatMapLatest { previewMode -> combine( - if (isInPreviewMode) { + if (previewMode.isInPreviewMode) { quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position) } else { quickAffordanceInteractor.quickAffordance(position = position) @@ -161,11 +175,18 @@ constructor( areQuickAffordancesFullyOpaque, selectedPreviewSlotId, ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId -> + val isSelected = selectedPreviewSlotId == position.toSlotId() model.toViewModel( - animateReveal = !isInPreviewMode && animateReveal, - isClickable = isFullyOpaque && !isInPreviewMode, + animateReveal = !previewMode.isInPreviewMode && animateReveal, + isClickable = isFullyOpaque && !previewMode.isInPreviewMode, isSelected = - (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()), + previewMode.isInPreviewMode && + previewMode.shouldHighlightSelectedAffordance && + isSelected, + isDimmed = + previewMode.isInPreviewMode && + previewMode.shouldHighlightSelectedAffordance && + !isSelected, ) } .distinctUntilChanged() @@ -176,6 +197,7 @@ constructor( animateReveal: Boolean, isClickable: Boolean, isSelected: Boolean, + isDimmed: Boolean, ): KeyguardQuickAffordanceViewModel { return when (this) { is KeyguardQuickAffordanceModel.Visible -> @@ -194,6 +216,7 @@ constructor( isActivated = activationState is ActivationState.Active, isSelected = isSelected, useLongPress = quickAffordanceInteractor.useLongPress, + isDimmed = isDimmed, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt index cf3a6daa40bb6..cb68a82118e2a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt @@ -31,6 +31,7 @@ data class KeyguardQuickAffordanceViewModel( val isActivated: Boolean = false, val isSelected: Boolean = false, val useLongPress: Boolean = false, + val isDimmed: Boolean = false, ) { data class OnClickedParameters( val configKey: String, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 022afdd61fc25..4b04b7b54dcd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -234,7 +234,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Test fun `startButton - in preview mode - visible even when keyguard not showing`() = testScope.runTest { - underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START) + underTest.enablePreviewMode( + initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + shouldHighlightSelectedAffordance = true, + ) repository.setKeyguardShowing(false) val latest = collectLastValue(underTest.startButton) @@ -263,12 +266,67 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { icon = icon, canShowWhileLocked = false, intent = Intent("action"), + isSelected = true, ), configKey = configKey, ) assertThat(latest()?.isSelected).isTrue() } + @Test + fun `endButton - in higlighted preview mode - dimmed when other is selected`() = + testScope.runTest { + underTest.enablePreviewMode( + initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + shouldHighlightSelectedAffordance = true, + ) + repository.setKeyguardShowing(false) + val startButton = collectLastValue(underTest.startButton) + val endButton = collectLastValue(underTest.endButton) + + val icon: Icon = mock() + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = + TestConfig( + isVisible = true, + isClickable = true, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + ), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_END, + testConfig = + TestConfig( + isVisible = true, + isClickable = true, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + ), + ) + + assertQuickAffordanceViewModel( + viewModel = endButton(), + testConfig = + TestConfig( + isVisible = true, + isClickable = false, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + isDimmed = true, + ), + configKey = configKey, + ) + } + @Test fun `endButton - present - visible model - do nothing on click`() = testScope.runTest { @@ -377,7 +435,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Test fun `alpha - in preview mode - does not change`() = testScope.runTest { - underTest.enablePreviewMode(null) + underTest.enablePreviewMode( + initiallySelectedSlotId = null, + shouldHighlightSelectedAffordance = false, + ) val value = collectLastValue(underTest.alpha) assertThat(value()).isEqualTo(1f) @@ -639,6 +700,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) + assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected) + assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( @@ -664,6 +727,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val icon: Icon? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, + val isSelected: Boolean = false, + val isDimmed: Boolean = false, ) { init { check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }