Merge "Split up KeyguardBottomArea" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c45ff2e900
@@ -65,17 +65,17 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/status_bar_expanded"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<!-- Root for all keyguard content. It was previously located within the shade. -->
|
||||
<com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
android:id="@id/keyguard_root_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/status_bar_expanded"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<!-- Shared container for the notification stack. Can be positioned by either
|
||||
the keyguard_root_view or notification_panel -->
|
||||
<com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
|
||||
@@ -823,6 +823,12 @@
|
||||
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
|
||||
<dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
|
||||
|
||||
<!-- The width/height/padding of the keyguard settings popup menu -->
|
||||
<dimen name="keyguard_settings_popup_menu_icon_height">24dp</dimen>
|
||||
<dimen name="keyguard_settings_popup_menu_icon_width">24dp</dimen>
|
||||
<dimen name="keyguard_settings_popup_menu_icon_end_margin">8dp</dimen>
|
||||
<dimen name="keyguard_settings_popup_menu_padding">12dp</dimen>
|
||||
|
||||
<!-- Amount the button should shake when it's not long-pressed for long enough. -->
|
||||
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.android.systemui.keyguard
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.android.systemui.CoreStartable
|
||||
@@ -24,18 +25,29 @@ import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
|
||||
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
|
||||
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
|
||||
import com.android.systemui.shade.NotificationShadeWindowView
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
|
||||
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
@@ -49,26 +61,44 @@ class KeyguardViewConfigurator
|
||||
constructor(
|
||||
private val keyguardRootView: KeyguardRootView,
|
||||
private val sharedNotificationContainer: SharedNotificationContainer,
|
||||
private val keyguardRootViewModel: KeyguardRootViewModel,
|
||||
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
|
||||
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
|
||||
private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
|
||||
private val notificationShadeWindowView: NotificationShadeWindowView,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val indicationController: KeyguardIndicationController,
|
||||
private val keyguardLayoutManager: KeyguardLayoutManager,
|
||||
private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
|
||||
private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
|
||||
private val falsingManager: FalsingManager,
|
||||
private val vibratorHelper: VibratorHelper,
|
||||
private val keyguardStateController: KeyguardStateController,
|
||||
private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
|
||||
private val activityStarter: ActivityStarter,
|
||||
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
|
||||
private val chipbarCoordinator: ChipbarCoordinator,
|
||||
) : CoreStartable {
|
||||
|
||||
private var rootViewHandle: DisposableHandle? = null
|
||||
private var indicationAreaHandle: DisposableHandle? = null
|
||||
private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
|
||||
private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
|
||||
private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
|
||||
private var settingsPopupMenuHandle: DisposableHandle? = null
|
||||
|
||||
override fun start() {
|
||||
bindKeyguardRootView()
|
||||
val notificationPanel =
|
||||
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
|
||||
bindIndicationArea(notificationPanel)
|
||||
unbindKeyguardBottomArea(notificationPanel)
|
||||
bindIndicationArea()
|
||||
bindLockIconView(notificationPanel)
|
||||
setupNotificationStackScrollLayout(notificationPanel)
|
||||
bindLeftShortcut()
|
||||
bindRightShortcut()
|
||||
bindAmbientIndicationArea()
|
||||
bindSettingsPopupMenu()
|
||||
|
||||
keyguardLayoutManager.layoutViews()
|
||||
keyguardLayoutManagerCommandListener.start()
|
||||
@@ -90,16 +120,21 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun bindIndicationArea(legacyParent: ViewGroup) {
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
leftShortcutHandle?.onConfigurationChanged()
|
||||
rightShortcutHandle?.onConfigurationChanged()
|
||||
ambientIndicationAreaHandle?.onConfigurationChanged()
|
||||
|
||||
keyguardLayoutManager.layoutViews()
|
||||
}
|
||||
|
||||
fun bindIndicationArea() {
|
||||
indicationAreaHandle?.dispose()
|
||||
|
||||
// At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
|
||||
// Disable one of them
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
|
||||
legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
|
||||
legacyParent.removeView(it)
|
||||
}
|
||||
} else {
|
||||
if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
|
||||
keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
|
||||
keyguardRootView.removeView(it)
|
||||
}
|
||||
@@ -109,10 +144,24 @@ constructor(
|
||||
KeyguardIndicationAreaBinder.bind(
|
||||
notificationShadeWindowView,
|
||||
keyguardIndicationAreaViewModel,
|
||||
indicationController
|
||||
keyguardRootViewModel,
|
||||
indicationController,
|
||||
featureFlags,
|
||||
)
|
||||
}
|
||||
|
||||
private fun bindKeyguardRootView() {
|
||||
rootViewHandle?.dispose()
|
||||
rootViewHandle = KeyguardRootViewBinder.bind(
|
||||
keyguardRootView,
|
||||
keyguardRootViewModel,
|
||||
featureFlags,
|
||||
occludingAppDeviceEntryMessageViewModel,
|
||||
chipbarCoordinator,
|
||||
keyguardStateController,
|
||||
)
|
||||
}
|
||||
|
||||
private fun bindLockIconView(legacyParent: ViewGroup) {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
|
||||
legacyParent.requireViewById<View>(R.id.lock_icon_view).let {
|
||||
@@ -125,12 +174,84 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindKeyguardRootView() {
|
||||
KeyguardRootViewBinder.bind(
|
||||
keyguardRootView,
|
||||
featureFlags,
|
||||
occludingAppDeviceEntryMessageViewModel,
|
||||
chipbarCoordinator,
|
||||
)
|
||||
private fun bindAmbientIndicationArea() {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
ambientIndicationAreaHandle?.destroy()
|
||||
ambientIndicationAreaHandle =
|
||||
KeyguardAmbientIndicationAreaViewBinder.bind(
|
||||
notificationShadeWindowView,
|
||||
keyguardAmbientIndicationViewModel,
|
||||
keyguardRootViewModel,
|
||||
)
|
||||
} else {
|
||||
keyguardRootView.findViewById<View?>(R.id.ambient_indication_container)?.let {
|
||||
keyguardRootView.removeView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindSettingsPopupMenu() {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
settingsPopupMenuHandle?.dispose()
|
||||
settingsPopupMenuHandle =
|
||||
KeyguardSettingsViewBinder.bind(
|
||||
keyguardRootView,
|
||||
keyguardSettingsMenuViewModel,
|
||||
vibratorHelper,
|
||||
activityStarter,
|
||||
)
|
||||
} else {
|
||||
keyguardRootView.findViewById<View?>(R.id.keyguard_settings_button)?.let {
|
||||
keyguardRootView.removeView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbindKeyguardBottomArea(legacyParent: ViewGroup) {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
legacyParent.requireViewById<View>(R.id.keyguard_bottom_area).let {
|
||||
legacyParent.removeView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindLeftShortcut() {
|
||||
leftShortcutHandle?.destroy()
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
leftShortcutHandle =
|
||||
KeyguardQuickAffordanceViewBinder.bind(
|
||||
keyguardRootView.requireViewById(R.id.start_button),
|
||||
keyguardQuickAffordancesCombinedViewModel.startButton,
|
||||
keyguardRootViewModel.alpha,
|
||||
falsingManager,
|
||||
vibratorHelper,
|
||||
) {
|
||||
indicationController.showTransientIndication(it)
|
||||
}
|
||||
} else {
|
||||
keyguardRootView.findViewById<View?>(R.id.start_button)?.let {
|
||||
keyguardRootView.removeView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindRightShortcut() {
|
||||
rightShortcutHandle?.destroy()
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
rightShortcutHandle =
|
||||
KeyguardQuickAffordanceViewBinder.bind(
|
||||
keyguardRootView.requireViewById(R.id.end_button),
|
||||
keyguardQuickAffordancesCombinedViewModel.endButton,
|
||||
keyguardRootViewModel.alpha,
|
||||
falsingManager,
|
||||
vibratorHelper,
|
||||
) {
|
||||
indicationController.showTransientIndication(it)
|
||||
}
|
||||
} else {
|
||||
keyguardRootView.findViewById<View?>(R.id.end_button)?.let {
|
||||
keyguardRootView.removeView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
|
||||
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
|
||||
import com.android.systemui.keyguard.shared.model.DozeStateModel
|
||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
|
||||
import com.android.systemui.keyguard.shared.model.ScreenModel
|
||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||
@@ -76,6 +77,8 @@ interface KeyguardRepository {
|
||||
*/
|
||||
val bottomAreaAlpha: StateFlow<Float>
|
||||
|
||||
val keyguardAlpha: StateFlow<Float>
|
||||
|
||||
/**
|
||||
* Observable of the relative offset of the lock-screen clock from its natural position on the
|
||||
* screen.
|
||||
@@ -170,6 +173,9 @@ interface KeyguardRepository {
|
||||
/** Whether quick settings or quick-quick settings is visible. */
|
||||
val isQuickSettingsVisible: Flow<Boolean>
|
||||
|
||||
/** Represents the current state of the KeyguardRootView visibility */
|
||||
val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
|
||||
|
||||
/** Receive an event for doze time tick */
|
||||
val dozeTimeTick: Flow<Unit>
|
||||
|
||||
@@ -194,8 +200,14 @@ interface KeyguardRepository {
|
||||
fun setAnimateDozingTransitions(animate: Boolean)
|
||||
|
||||
/** Sets the current amount of alpha that should be used for rendering the bottom area. */
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
fun setBottomAreaAlpha(alpha: Float)
|
||||
|
||||
/** Sets the current amount of alpha that should be used for rendering the keyguard. */
|
||||
fun setKeyguardAlpha(alpha: Float)
|
||||
|
||||
fun setKeyguardVisibility(statusBarState: Int, goingToFullShade: Boolean, occlusionTransitionRunning: Boolean)
|
||||
|
||||
/**
|
||||
* Sets the relative offset of the lock-screen clock from its natural position on the screen.
|
||||
*/
|
||||
@@ -244,6 +256,9 @@ constructor(
|
||||
private val _bottomAreaAlpha = MutableStateFlow(1f)
|
||||
override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
|
||||
|
||||
private val _keyguardAlpha = MutableStateFlow(1f)
|
||||
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
|
||||
|
||||
private val _clockPosition = MutableStateFlow(Position(0, 0))
|
||||
override val clockPosition = _clockPosition.asStateFlow()
|
||||
|
||||
@@ -686,6 +701,17 @@ constructor(
|
||||
private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
|
||||
override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
|
||||
|
||||
private val _keyguardRootViewVisibility =
|
||||
MutableStateFlow(
|
||||
KeyguardRootViewVisibilityState(
|
||||
com.android.systemui.statusbar.StatusBarState.SHADE,
|
||||
goingToFullShade = false,
|
||||
occlusionTransitionRunning = false,
|
||||
)
|
||||
)
|
||||
override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
|
||||
_keyguardRootViewVisibility.asStateFlow()
|
||||
|
||||
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||
_animateBottomAreaDozingTransitions.value = animate
|
||||
}
|
||||
@@ -694,6 +720,23 @@ constructor(
|
||||
_bottomAreaAlpha.value = alpha
|
||||
}
|
||||
|
||||
override fun setKeyguardAlpha(alpha: Float) {
|
||||
_keyguardAlpha.value = alpha
|
||||
}
|
||||
|
||||
override fun setKeyguardVisibility(
|
||||
statusBarState: Int,
|
||||
goingToFullShade: Boolean,
|
||||
occlusionTransitionRunning: Boolean
|
||||
) {
|
||||
_keyguardRootViewVisibility.value =
|
||||
KeyguardRootViewVisibilityState(
|
||||
statusBarState,
|
||||
goingToFullShade,
|
||||
occlusionTransitionRunning
|
||||
)
|
||||
}
|
||||
|
||||
override fun setClockPosition(x: Int, y: Int) {
|
||||
_clockPosition.value = Position(x, y)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.graphics.Point
|
||||
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.common.shared.model.Position
|
||||
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
@@ -32,14 +33,17 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
|
||||
import com.android.systemui.keyguard.shared.model.DozeStateModel
|
||||
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
|
||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
|
||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.util.kotlin.sample
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -63,6 +67,18 @@ constructor(
|
||||
bouncerRepository: KeyguardBouncerRepository,
|
||||
configurationRepository: ConfigurationRepository,
|
||||
) {
|
||||
|
||||
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.
|
||||
*/
|
||||
val previewMode = MutableStateFlow(PreviewMode())
|
||||
/**
|
||||
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
|
||||
* all.
|
||||
@@ -142,8 +158,6 @@ constructor(
|
||||
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
|
||||
/** Observable for the [StatusBarState] */
|
||||
val statusBarState: Flow<StatusBarState> = repository.statusBarState
|
||||
/** Whether or not quick settings or quick quick settings are showing. */
|
||||
val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
|
||||
/**
|
||||
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
|
||||
* side, under display) is used to unlock the device.
|
||||
@@ -182,6 +196,18 @@ constructor(
|
||||
/** Notifies when a new configuration is set */
|
||||
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
|
||||
|
||||
/** Represents the current state of the KeyguardRootView visibility */
|
||||
val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
|
||||
repository.keyguardRootViewVisibility
|
||||
|
||||
/** The position of the keyguard clock. */
|
||||
val clockPosition: Flow<Position> = repository.clockPosition
|
||||
|
||||
val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
|
||||
|
||||
/** Whether to animate the next doze mode transition. */
|
||||
val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
|
||||
|
||||
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
|
||||
return dozeTransitionModel.filter { states.contains(it.to) }
|
||||
}
|
||||
@@ -211,6 +237,22 @@ constructor(
|
||||
repository.setQuickSettingsVisible(isVisible)
|
||||
}
|
||||
|
||||
fun setKeyguardRootVisibility(statusBarState: Int, goingToFullShade: Boolean, isOcclusionTransitionRunning: Boolean) {
|
||||
repository.setKeyguardVisibility(statusBarState, goingToFullShade, isOcclusionTransitionRunning)
|
||||
}
|
||||
|
||||
fun setClockPosition(x: Int, y: Int) {
|
||||
repository.setClockPosition(x, y)
|
||||
}
|
||||
|
||||
fun setAlpha(alpha: Float) {
|
||||
repository.setKeyguardAlpha(alpha)
|
||||
}
|
||||
|
||||
fun setAnimateDozingTransitions(animate: Boolean) {
|
||||
repository.setAnimateDozingTransitions(animate)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyguardInteractor"
|
||||
}
|
||||
|
||||
@@ -101,10 +101,9 @@ constructor(
|
||||
quickAffordanceAlwaysVisible(position),
|
||||
keyguardInteractor.isDozing,
|
||||
keyguardInteractor.isKeyguardShowing,
|
||||
keyguardInteractor.isQuickSettingsVisible,
|
||||
biometricSettingsRepository.isCurrentUserInLockdown,
|
||||
) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
|
||||
if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible && !isUserInLockdown) {
|
||||
) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
|
||||
if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
|
||||
affordance
|
||||
} else {
|
||||
KeyguardQuickAffordanceModel.Hidden
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.keyguard.shared.model
|
||||
|
||||
/**
|
||||
* Provides a stateful representation of the visibility of the KeyguardRootView
|
||||
*
|
||||
* @param statusBarState State of the status bar represented by [StatusBarState]
|
||||
* @param goingToFullShade Whether status bar is going to full shade
|
||||
* @param occlusionTransitionRunning Whether the occlusion transition is running in this instant
|
||||
*/
|
||||
data class KeyguardRootViewVisibilityState(
|
||||
val statusBarState: Int,
|
||||
val goingToFullShade: Boolean,
|
||||
val occlusionTransitionRunning: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.keyguard.ui.binder
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewPropertyAnimator
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object KeyguardAmbientIndicationAreaViewBinder {
|
||||
/**
|
||||
* Defines interface for an object that acts as the binding between the view and its view-model.
|
||||
*
|
||||
* Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
|
||||
* it is bound.
|
||||
*/
|
||||
interface Binding {
|
||||
/**
|
||||
* Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
|
||||
* indication areas.
|
||||
*/
|
||||
fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
|
||||
|
||||
/** Notifies that device configuration has changed. */
|
||||
fun onConfigurationChanged()
|
||||
|
||||
/** Destroys this binding, releases resources, and cancels any coroutines. */
|
||||
fun destroy()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: KeyguardAmbientIndicationViewModel,
|
||||
keyguardRootViewModel: KeyguardRootViewModel,
|
||||
): Binding {
|
||||
val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
|
||||
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
|
||||
|
||||
val disposableHandle =
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
keyguardRootViewModel.alpha.collect { alpha ->
|
||||
view.importantForAccessibility =
|
||||
if (alpha == 0f) {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
||||
}
|
||||
|
||||
ambientIndicationArea?.alpha = alpha
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.indicationAreaTranslationX.collect { translationX ->
|
||||
ambientIndicationArea?.translationX = translationX
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
configurationBasedDimensions
|
||||
.map { it.defaultBurnInPreventionYOffsetPx }
|
||||
.flatMapLatest { defaultBurnInOffsetY ->
|
||||
viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
|
||||
}
|
||||
.collect { translationY ->
|
||||
ambientIndicationArea?.translationY = translationY
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return object : Binding {
|
||||
override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
|
||||
return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged() {
|
||||
configurationBasedDimensions.value = loadFromResources(view)
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
disposableHandle.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
||||
return ConfigurationBasedDimensions(
|
||||
defaultBurnInPreventionYOffsetPx =
|
||||
view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
|
||||
)
|
||||
}
|
||||
|
||||
private data class ConfigurationBasedDimensions(
|
||||
val defaultBurnInPreventionYOffsetPx: Int,
|
||||
)
|
||||
}
|
||||
@@ -63,6 +63,7 @@ import kotlinx.coroutines.launch
|
||||
* view-model to be reused for multiple view/view-binder bindings.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
object KeyguardBottomAreaViewBinder {
|
||||
|
||||
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
|
||||
@@ -75,6 +76,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
* Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
|
||||
* it is bound.
|
||||
*/
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
interface Binding {
|
||||
/**
|
||||
* Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
|
||||
@@ -96,6 +99,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
|
||||
/** Binds the view to the view-model, continuing to update the former based on the latter. */
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
@@ -132,6 +136,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
val disposableHandle =
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
launch {
|
||||
viewModel.startButton.collect { buttonModel ->
|
||||
updateButton(
|
||||
@@ -144,6 +150,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
launch {
|
||||
viewModel.endButton.collect { buttonModel ->
|
||||
updateButton(
|
||||
@@ -180,6 +187,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
launch {
|
||||
updateButtonAlpha(
|
||||
view = startButton,
|
||||
@@ -188,6 +196,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
)
|
||||
}
|
||||
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
launch {
|
||||
updateButtonAlpha(
|
||||
view = endButton,
|
||||
@@ -213,6 +222,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
launch {
|
||||
configurationBasedDimensions.collect { dimensions ->
|
||||
startButton.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||
@@ -287,6 +297,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun updateButton(
|
||||
view: ImageView,
|
||||
@@ -394,6 +406,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
view.isSelected = viewModel.isSelected
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
private suspend fun updateButtonAlpha(
|
||||
view: View,
|
||||
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
|
||||
@@ -405,6 +419,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
.collect { view.alpha = it }
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
private fun View.animateVisibility(visible: Boolean) {
|
||||
animate()
|
||||
.withStartAction {
|
||||
@@ -422,6 +437,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
.start()
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
private class OnLongClickListener(
|
||||
private val falsingManager: FalsingManager?,
|
||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||
@@ -455,9 +472,10 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
|
||||
override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
|
||||
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
private class OnClickListener(
|
||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||
private val falsingManager: FalsingManager,
|
||||
@@ -479,6 +497,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
||||
return ConfigurationBasedDimensions(
|
||||
defaultBurnInPreventionYOffsetPx =
|
||||
@@ -491,6 +510,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
/** Opens the wallpaper picker screen after the device is unlocked by the user. */
|
||||
private fun navigateToLockScreenSettings(
|
||||
activityStarter: ActivityStarter,
|
||||
@@ -510,6 +530,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||
private data class ConfigurationBasedDimensions(
|
||||
val defaultBurnInPreventionYOffsetPx: Int,
|
||||
val buttonSizePx: Size,
|
||||
|
||||
@@ -23,7 +23,10 @@ import android.widget.TextView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
@@ -49,7 +52,9 @@ object KeyguardIndicationAreaBinder {
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: KeyguardIndicationAreaViewModel,
|
||||
keyguardRootViewModel: KeyguardRootViewModel,
|
||||
indicationController: KeyguardIndicationController,
|
||||
featureFlags: FeatureFlags,
|
||||
): DisposableHandle {
|
||||
val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
|
||||
indicationController.setIndicationArea(indicationArea)
|
||||
@@ -66,15 +71,28 @@ object KeyguardIndicationAreaBinder {
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.alpha.collect { alpha ->
|
||||
view.importantForAccessibility =
|
||||
if (alpha == 0f) {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
||||
}
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
keyguardRootViewModel.alpha.collect { alpha ->
|
||||
view.importantForAccessibility =
|
||||
if (alpha == 0f) {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
||||
}
|
||||
|
||||
indicationArea.alpha = alpha
|
||||
indicationArea.alpha = alpha
|
||||
}
|
||||
} else {
|
||||
viewModel.alpha.collect { alpha ->
|
||||
view.importantForAccessibility =
|
||||
if (alpha == 0f) {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
||||
}
|
||||
|
||||
indicationArea.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.keyguard.ui.binder
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.Animatable2
|
||||
import android.util.Size
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.app.animation.Interpolators
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.animation.view.LaunchableImageView
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* This is only for a SINGLE Quick affordance
|
||||
*/
|
||||
object KeyguardQuickAffordanceViewBinder {
|
||||
|
||||
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.
|
||||
*
|
||||
* Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
|
||||
* it is bound.
|
||||
*/
|
||||
interface Binding {
|
||||
/** Notifies that device configuration has changed. */
|
||||
fun onConfigurationChanged()
|
||||
|
||||
/** Destroys this binding, releases resources, and cancels any coroutines. */
|
||||
fun destroy()
|
||||
}
|
||||
|
||||
fun bind(
|
||||
view: LaunchableImageView,
|
||||
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
|
||||
alpha: Flow<Float>,
|
||||
falsingManager: FalsingManager?,
|
||||
vibratorHelper: VibratorHelper?,
|
||||
messageDisplayer: (Int) -> Unit,
|
||||
): Binding {
|
||||
val button = view as ImageView
|
||||
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
|
||||
val disposableHandle =
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.collect { buttonModel ->
|
||||
updateButton(
|
||||
view = button,
|
||||
viewModel = buttonModel,
|
||||
falsingManager = falsingManager,
|
||||
messageDisplayer = messageDisplayer,
|
||||
vibratorHelper = vibratorHelper,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
updateButtonAlpha(
|
||||
view = button,
|
||||
viewModel = viewModel,
|
||||
alphaFlow = alpha,
|
||||
)
|
||||
}
|
||||
|
||||
launch {
|
||||
configurationBasedDimensions.collect { dimensions ->
|
||||
button.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||
width = dimensions.buttonSizePx.width
|
||||
height = dimensions.buttonSizePx.height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return object : Binding {
|
||||
override fun onConfigurationChanged() {
|
||||
configurationBasedDimensions.value = loadFromResources(view)
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
disposableHandle.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun updateButton(
|
||||
view: ImageView,
|
||||
viewModel: KeyguardQuickAffordanceViewModel,
|
||||
falsingManager: FalsingManager?,
|
||||
messageDisplayer: (Int) -> Unit,
|
||||
vibratorHelper: VibratorHelper?,
|
||||
) {
|
||||
if (!viewModel.isVisible) {
|
||||
view.isInvisible = true
|
||||
return
|
||||
}
|
||||
|
||||
if (!view.isVisible) {
|
||||
view.isVisible = true
|
||||
if (viewModel.animateReveal) {
|
||||
view.alpha = 0f
|
||||
view.translationY = view.height / 2f
|
||||
view
|
||||
.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0f)
|
||||
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
|
||||
.setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
|
||||
.start()
|
||||
}
|
||||
}
|
||||
|
||||
IconViewBinder.bind(viewModel.icon, view)
|
||||
|
||||
(view.drawable as? Animatable2)?.let { animatable ->
|
||||
(viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
|
||||
// Always start the animation (we do call stop() below, if we need to skip it).
|
||||
animatable.start()
|
||||
|
||||
if (view.tag != iconResourceId) {
|
||||
// Here when we haven't run the animation on a previous update.
|
||||
//
|
||||
// Save the resource ID for next time, so we know not to re-animate the same
|
||||
// animation again.
|
||||
view.tag = iconResourceId
|
||||
} else {
|
||||
// Here when we've already done this animation on a previous update and want to
|
||||
// skip directly to the final frame of the animation to avoid running it.
|
||||
//
|
||||
// By calling stop after start, we go to the final frame of the animation.
|
||||
animatable.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.isActivated = viewModel.isActivated
|
||||
view.drawable.setTint(
|
||||
Utils.getColorAttrDefaultColor(
|
||||
view.context,
|
||||
if (viewModel.isActivated) {
|
||||
com.android.internal.R.attr.materialColorOnPrimaryFixed
|
||||
} else {
|
||||
com.android.internal.R.attr.materialColorOnSurface
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
view.backgroundTintList =
|
||||
if (!viewModel.isSelected) {
|
||||
Utils.getColorAttr(
|
||||
view.context,
|
||||
if (viewModel.isActivated) {
|
||||
com.android.internal.R.attr.materialColorPrimaryFixed
|
||||
} else {
|
||||
com.android.internal.R.attr.materialColorSurfaceContainerHigh
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
view
|
||||
.animate()
|
||||
.scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
|
||||
.scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
|
||||
.start()
|
||||
|
||||
view.isClickable = viewModel.isClickable
|
||||
if (viewModel.isClickable) {
|
||||
if (viewModel.useLongPress) {
|
||||
val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
|
||||
view,
|
||||
viewModel,
|
||||
messageDisplayer,
|
||||
vibratorHelper,
|
||||
falsingManager,
|
||||
)
|
||||
view.setOnTouchListener(onTouchListener)
|
||||
view.onLongClickListener =
|
||||
OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
|
||||
} else {
|
||||
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
|
||||
}
|
||||
} else {
|
||||
view.onLongClickListener = null
|
||||
view.setOnClickListener(null)
|
||||
view.setOnTouchListener(null)
|
||||
}
|
||||
|
||||
view.isSelected = viewModel.isSelected
|
||||
}
|
||||
|
||||
private suspend fun updateButtonAlpha(
|
||||
view: View,
|
||||
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
|
||||
alphaFlow: Flow<Float>,
|
||||
) {
|
||||
combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
|
||||
if (isDimmed) DIM_ALPHA else alpha
|
||||
}
|
||||
.collect { view.alpha = it }
|
||||
}
|
||||
|
||||
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
||||
return ConfigurationBasedDimensions(
|
||||
buttonSizePx =
|
||||
Size(
|
||||
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
|
||||
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private class OnClickListener(
|
||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||
private val falsingManager: FalsingManager,
|
||||
) : View.OnClickListener {
|
||||
override fun onClick(view: View) {
|
||||
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (viewModel.configKey != null) {
|
||||
viewModel.onClicked(
|
||||
KeyguardQuickAffordanceViewModel.OnClickedParameters(
|
||||
configKey = viewModel.configKey,
|
||||
expandable = Expandable.fromView(view),
|
||||
slotId = viewModel.slotId,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OnLongClickListener(
|
||||
private val falsingManager: FalsingManager?,
|
||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||
private val vibratorHelper: VibratorHelper?,
|
||||
private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
|
||||
) : View.OnLongClickListener {
|
||||
override fun onLongClick(view: View): Boolean {
|
||||
if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (viewModel.configKey != null) {
|
||||
viewModel.onClicked(
|
||||
KeyguardQuickAffordanceViewModel.OnClickedParameters(
|
||||
configKey = viewModel.configKey,
|
||||
expandable = Expandable.fromView(view),
|
||||
slotId = viewModel.slotId,
|
||||
)
|
||||
)
|
||||
vibratorHelper?.vibrate(
|
||||
if (viewModel.isActivated) {
|
||||
KeyguardBottomAreaVibrations.Activated
|
||||
} else {
|
||||
KeyguardBottomAreaVibrations.Deactivated
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
onTouchListener.cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
|
||||
|
||||
}
|
||||
|
||||
private data class ConfigurationBasedDimensions(
|
||||
val buttonSizePx: Size,
|
||||
)
|
||||
|
||||
}
|
||||
@@ -17,20 +17,26 @@
|
||||
package com.android.systemui.keyguard.ui.binder
|
||||
|
||||
import android.annotation.DrawableRes
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.app.animation.Interpolators
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.shared.model.Text
|
||||
import com.android.systemui.common.shared.model.TintedIcon
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.StatusBarState
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -40,31 +46,74 @@ object KeyguardRootViewBinder {
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: KeyguardRootViewModel,
|
||||
featureFlags: FeatureFlags,
|
||||
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
|
||||
chipbarCoordinator: ChipbarCoordinator,
|
||||
) {
|
||||
if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
|
||||
keyguardStateController: KeyguardStateController,
|
||||
): DisposableHandle {
|
||||
val disposableHandle =
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
|
||||
->
|
||||
if (biometricMessage?.message != null) {
|
||||
chipbarCoordinator.displayView(
|
||||
createChipbarInfo(
|
||||
biometricMessage.message,
|
||||
R.drawable.ic_lock,
|
||||
if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
occludingAppDeviceEntryMessageViewModel.message.collect {
|
||||
biometricMessage ->
|
||||
if (biometricMessage?.message != null) {
|
||||
chipbarCoordinator.displayView(
|
||||
createChipbarInfo(
|
||||
biometricMessage.message,
|
||||
R.drawable.ic_lock,
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
|
||||
} else {
|
||||
chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
|
||||
view.animate().cancel()
|
||||
val goingToFullShade = visibilityState.goingToFullShade
|
||||
val statusBarState = visibilityState.statusBarState
|
||||
val isOcclusionTransitionRunning =
|
||||
visibilityState.occlusionTransitionRunning
|
||||
if (goingToFullShade) {
|
||||
view.animate().alpha(0f).setStartDelay(
|
||||
keyguardStateController.keyguardFadingAwayDelay
|
||||
).setDuration(
|
||||
keyguardStateController.shortenedFadingAwayDuration
|
||||
).setInterpolator(
|
||||
Interpolators.ALPHA_OUT
|
||||
).withEndAction { view.visibility = View.GONE }.start()
|
||||
} else if (
|
||||
statusBarState == StatusBarState.KEYGUARD ||
|
||||
statusBarState == StatusBarState.SHADE_LOCKED
|
||||
) {
|
||||
view.visibility = View.VISIBLE
|
||||
if (!isOcclusionTransitionRunning) {
|
||||
view.alpha = 1f
|
||||
}
|
||||
} else {
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.alpha.collect { alpha ->
|
||||
view.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return disposableHandle
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,10 +122,10 @@ object KeyguardRootViewBinder {
|
||||
private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
|
||||
return ChipbarInfo(
|
||||
startIcon =
|
||||
TintedIcon(
|
||||
Icon.Resource(icon, null),
|
||||
ChipbarInfo.DEFAULT_ICON_TINT,
|
||||
),
|
||||
TintedIcon(
|
||||
Icon.Resource(icon, null),
|
||||
ChipbarInfo.DEFAULT_ICON_TINT,
|
||||
),
|
||||
text = Text.Loaded(message),
|
||||
endItem = null,
|
||||
vibrationEffect = null,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.keyguard.ui.binder
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||
import com.android.systemui.animation.view.LaunchableLinearLayout
|
||||
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||
import com.android.systemui.common.ui.binder.TextViewBinder
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object KeyguardSettingsViewBinder {
|
||||
fun bind(
|
||||
parentView: View,
|
||||
viewModel: KeyguardSettingsMenuViewModel,
|
||||
vibratorHelper: VibratorHelper,
|
||||
activityStarter: ActivityStarter
|
||||
): DisposableHandle {
|
||||
val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
|
||||
|
||||
val disposableHandle =
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.isVisible.distinctUntilChanged().collect { isVisible ->
|
||||
view.animateVisibility(visible = isVisible)
|
||||
if (isVisible) {
|
||||
vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
|
||||
view.setOnTouchListener(
|
||||
KeyguardSettingsButtonOnTouchListener(
|
||||
view = view,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
)
|
||||
IconViewBinder.bind(
|
||||
icon = viewModel.icon,
|
||||
view = view.requireViewById(R.id.icon),
|
||||
)
|
||||
TextViewBinder.bind(
|
||||
view = view.requireViewById(R.id.text),
|
||||
viewModel = viewModel.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// activityStarter will only be null when rendering the preview that
|
||||
// shows up in the Wallpaper Picker app. If we do that, then the
|
||||
// settings menu should never be visible.
|
||||
if (activityStarter != null) {
|
||||
launch {
|
||||
viewModel.shouldOpenSettings
|
||||
.filter { it }
|
||||
.collect {
|
||||
navigateToLockScreenSettings(
|
||||
activityStarter = activityStarter,
|
||||
view = view,
|
||||
)
|
||||
viewModel.onSettingsShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return disposableHandle
|
||||
}
|
||||
|
||||
/** Opens the wallpaper picker screen after the device is unlocked by the user. */
|
||||
private fun navigateToLockScreenSettings(
|
||||
activityStarter: ActivityStarter,
|
||||
view: View,
|
||||
) {
|
||||
activityStarter.postStartActivityDismissingKeyguard(
|
||||
Intent(Intent.ACTION_SET_WALLPAPER).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
view.context
|
||||
.getString(R.string.config_wallpaperPickerPackage)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let { packageName -> setPackage(packageName) }
|
||||
},
|
||||
/* delay= */ 0,
|
||||
/* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
|
||||
/* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
|
||||
)
|
||||
}
|
||||
|
||||
private fun View.animateVisibility(visible: Boolean) {
|
||||
animate()
|
||||
.withStartAction {
|
||||
if (visible) {
|
||||
alpha = 0f
|
||||
isVisible = true
|
||||
}
|
||||
}
|
||||
.alpha(if (visible) 1f else 0f)
|
||||
.withEndAction {
|
||||
if (!visible) {
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,17 +42,27 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||
import com.android.systemui.monet.ColorScheme
|
||||
import com.android.systemui.plugins.ClockController
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.shared.clocks.ClockRegistry
|
||||
import com.android.systemui.shared.clocks.DefaultClockController
|
||||
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
|
||||
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||
import com.android.systemui.statusbar.VibratorHelper
|
||||
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
|
||||
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
|
||||
import dagger.assisted.Assisted
|
||||
@@ -71,6 +81,7 @@ constructor(
|
||||
private val clockViewModel: KeyguardPreviewClockViewModel,
|
||||
private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
|
||||
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
|
||||
private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
|
||||
displayManager: DisplayManager,
|
||||
private val windowManager: WindowManager,
|
||||
private val clockController: ClockEventController,
|
||||
@@ -78,6 +89,12 @@ constructor(
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
|
||||
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val keyguardLayoutManager: KeyguardLayoutManager,
|
||||
private val falsingManager: FalsingManager,
|
||||
private val vibratorHelper: VibratorHelper,
|
||||
private val indicationController: KeyguardIndicationController,
|
||||
private val keyguardRootViewModel: KeyguardRootViewModel,
|
||||
@Assisted bundle: Bundle,
|
||||
) {
|
||||
|
||||
@@ -106,14 +123,26 @@ constructor(
|
||||
private val disposables = mutableSetOf<DisposableHandle>()
|
||||
private var isDestroyed = false
|
||||
|
||||
private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
|
||||
|
||||
init {
|
||||
bottomAreaViewModel.enablePreviewMode(
|
||||
initiallySelectedSlotId =
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
keyguardRootViewModel.enablePreviewMode(
|
||||
initiallySelectedSlotId =
|
||||
bundle.getString(
|
||||
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
|
||||
),
|
||||
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||
)
|
||||
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||
)
|
||||
} else {
|
||||
bottomAreaViewModel.enablePreviewMode(
|
||||
initiallySelectedSlotId =
|
||||
bundle.getString(
|
||||
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
|
||||
),
|
||||
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||
)
|
||||
}
|
||||
runBlocking(mainDispatcher) {
|
||||
host =
|
||||
SurfaceControlViewHost(
|
||||
@@ -130,7 +159,20 @@ constructor(
|
||||
mainHandler.post {
|
||||
val rootView = FrameLayout(context)
|
||||
|
||||
setUpBottomArea(rootView)
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
val keyguardRootView = KeyguardRootView(context, null)
|
||||
rootView.addView(
|
||||
keyguardRootView,
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
),
|
||||
)
|
||||
setupShortcuts(keyguardRootView)
|
||||
keyguardLayoutManager.layoutViews(keyguardRootView)
|
||||
} else {
|
||||
setUpBottomArea(rootView)
|
||||
}
|
||||
|
||||
setUpSmartspace(rootView)
|
||||
smartSpaceView?.let {
|
||||
@@ -182,13 +224,20 @@ constructor(
|
||||
}
|
||||
|
||||
fun onSlotSelected(slotId: String) {
|
||||
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||
} else {
|
||||
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
isDestroyed = true
|
||||
lockscreenSmartspaceController.disconnect()
|
||||
disposables.forEach { it.dispose() }
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
shortcutsBindings.forEach { it.destroy() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,6 +299,7 @@ constructor(
|
||||
smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
private fun setUpBottomArea(parentView: ViewGroup) {
|
||||
val bottomAreaView =
|
||||
LayoutInflater.from(context)
|
||||
@@ -259,7 +309,7 @@ constructor(
|
||||
false,
|
||||
) as KeyguardBottomAreaView
|
||||
bottomAreaView.init(
|
||||
viewModel = bottomAreaViewModel,
|
||||
viewModel = bottomAreaViewModel,
|
||||
)
|
||||
parentView.addView(
|
||||
bottomAreaView,
|
||||
@@ -270,6 +320,32 @@ constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupShortcuts(keyguardRootView: KeyguardRootView) {
|
||||
shortcutsBindings.add(
|
||||
KeyguardQuickAffordanceViewBinder.bind(
|
||||
keyguardRootView.requireViewById(R.id.start_button),
|
||||
quickAffordancesCombinedViewModel.startButton,
|
||||
keyguardRootViewModel.alpha,
|
||||
falsingManager,
|
||||
vibratorHelper,
|
||||
) {
|
||||
indicationController.showTransientIndication(it)
|
||||
}
|
||||
)
|
||||
|
||||
shortcutsBindings.add(
|
||||
KeyguardQuickAffordanceViewBinder.bind(
|
||||
keyguardRootView.requireViewById(R.id.end_button),
|
||||
quickAffordancesCombinedViewModel.endButton,
|
||||
keyguardRootViewModel.alpha,
|
||||
falsingManager,
|
||||
vibratorHelper,
|
||||
) {
|
||||
indicationController.showTransientIndication(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUpUdfps(parentView: ViewGroup) {
|
||||
val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
|
||||
|
||||
|
||||
@@ -20,8 +20,13 @@ package com.android.systemui.keyguard.ui.view
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.android.keyguard.LockIconView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.view.LaunchableImageView
|
||||
|
||||
/** Provides a container for all keyguard ui content. */
|
||||
class KeyguardRootView(
|
||||
@@ -36,6 +41,10 @@ class KeyguardRootView(
|
||||
init {
|
||||
addIndicationTextArea()
|
||||
addLockIconView()
|
||||
addAmbientIndicationArea()
|
||||
addLeftShortcut()
|
||||
addRightShortcut()
|
||||
addSettingsPopupMenu()
|
||||
}
|
||||
|
||||
private fun addIndicationTextArea() {
|
||||
@@ -47,4 +56,65 @@ class KeyguardRootView(
|
||||
val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
|
||||
addView(view)
|
||||
}
|
||||
|
||||
private fun addAmbientIndicationArea() {
|
||||
LayoutInflater.from(context).inflate(R.layout.ambient_indication, this)
|
||||
}
|
||||
|
||||
private fun addLeftShortcut() {
|
||||
val view = LaunchableImageView(context, attrs)
|
||||
.apply {
|
||||
id = R.id.start_button
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
background =
|
||||
ResourcesCompat.getDrawable(
|
||||
context.resources,
|
||||
R.drawable.keyguard_bottom_affordance_bg,
|
||||
context.theme
|
||||
)
|
||||
foreground =
|
||||
ResourcesCompat.getDrawable(
|
||||
context.resources,
|
||||
R.drawable.keyguard_bottom_affordance_selected_border,
|
||||
context.theme
|
||||
)
|
||||
visibility = View.INVISIBLE
|
||||
}
|
||||
addView(view)
|
||||
}
|
||||
|
||||
private fun addRightShortcut() {
|
||||
val view = LaunchableImageView(context, attrs)
|
||||
.apply {
|
||||
id = R.id.end_button
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
background =
|
||||
ResourcesCompat.getDrawable(
|
||||
context.resources,
|
||||
R.drawable.keyguard_bottom_affordance_bg,
|
||||
context.theme
|
||||
)
|
||||
foreground =
|
||||
ResourcesCompat.getDrawable(
|
||||
context.resources,
|
||||
R.drawable.keyguard_bottom_affordance_selected_border,
|
||||
context.theme
|
||||
)
|
||||
visibility = View.INVISIBLE
|
||||
}
|
||||
addView(view)
|
||||
}
|
||||
|
||||
private fun addSettingsPopupMenu() {
|
||||
val view = LayoutInflater.from(context).inflate(
|
||||
R.layout.keyguard_settings_popup_menu,
|
||||
this,
|
||||
false
|
||||
)
|
||||
.apply {
|
||||
id = R.id.keyguard_settings_button
|
||||
visibility = GONE
|
||||
}
|
||||
addView(view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,27 @@ import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
|
||||
import androidx.constraintlayout.widget.ConstraintSet.END
|
||||
import androidx.constraintlayout.widget.ConstraintSet.LEFT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
||||
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.view.LaunchableLinearLayout
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
@@ -136,15 +146,139 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutShortcuts(rootView: KeyguardRootView) {
|
||||
val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
|
||||
val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
|
||||
val width =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
|
||||
val height =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
|
||||
val horizontalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
|
||||
val verticalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
|
||||
val padding =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
|
||||
|
||||
leftShortcut.apply {
|
||||
updateLayoutParams<MarginLayoutParams> {
|
||||
marginStart = horizontalOffsetMargin
|
||||
bottomMargin = verticalOffsetMargin
|
||||
}
|
||||
setPadding(padding)
|
||||
}
|
||||
|
||||
rightShortcut.apply {
|
||||
updateLayoutParams<MarginLayoutParams> {
|
||||
marginEnd = horizontalOffsetMargin
|
||||
bottomMargin = verticalOffsetMargin
|
||||
}
|
||||
setPadding(padding)
|
||||
}
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(leftShortcut.id, width)
|
||||
constrainHeight(leftShortcut.id, height)
|
||||
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
|
||||
constrainWidth(rightShortcut.id, width)
|
||||
constrainHeight(rightShortcut.id, height)
|
||||
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
|
||||
connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
|
||||
val ambientIndicationContainer =
|
||||
rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
|
||||
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
|
||||
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
|
||||
|
||||
if (keyguardUpdateMonitor.isUdfpsSupported) {
|
||||
//constrain below udfps and above indication area
|
||||
constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
|
||||
connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
|
||||
connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
|
||||
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
|
||||
} else {
|
||||
//constrain above lock icon
|
||||
constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
|
||||
connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
|
||||
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
|
||||
}
|
||||
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
|
||||
val popupMenu =
|
||||
rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
|
||||
val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
|
||||
val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
|
||||
val horizontalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
|
||||
|
||||
icon.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
|
||||
width =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
|
||||
marginEnd =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
|
||||
}
|
||||
|
||||
textView.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height = WRAP_CONTENT
|
||||
width = WRAP_CONTENT
|
||||
}
|
||||
|
||||
popupMenu.updateLayoutParams<MarginLayoutParams> {
|
||||
bottomMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
|
||||
marginStart = horizontalOffsetMargin
|
||||
marginEnd = horizontalOffsetMargin
|
||||
}
|
||||
popupMenu.setPadding(
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
|
||||
)
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(popupMenu.id, WRAP_CONTENT)
|
||||
constrainHeight(popupMenu.id, WRAP_CONTENT)
|
||||
constrainMinHeight(
|
||||
popupMenu.id,
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
|
||||
)
|
||||
connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
|
||||
connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.dp(): Int {
|
||||
return context.resources.getDimensionPixelSize(this)
|
||||
}
|
||||
|
||||
private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
|
||||
val cs = ConstraintSet()
|
||||
cs.clone(this)
|
||||
return cs
|
||||
}
|
||||
private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
|
||||
ConstraintSet().also {
|
||||
it.clone(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT = "default"
|
||||
|
||||
@@ -63,8 +63,8 @@ constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
fun layoutViews() {
|
||||
layout?.layoutViews(keyguardRootView)
|
||||
fun layoutViews(rootView: KeyguardRootView = keyguardRootView) {
|
||||
layout?.layoutViews(rootView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -85,7 +85,13 @@ interface LockscreenLayout {
|
||||
.applyTo(rootView)
|
||||
layoutIndicationArea(rootView)
|
||||
layoutLockIcon(rootView)
|
||||
layoutShortcuts(rootView)
|
||||
layoutAmbientIndicationArea(rootView)
|
||||
layoutSettingsPopupMenu(rootView)
|
||||
}
|
||||
fun layoutIndicationArea(rootView: KeyguardRootView)
|
||||
fun layoutLockIcon(rootView: KeyguardRootView)
|
||||
fun layoutShortcuts(rootView: KeyguardRootView)
|
||||
fun layoutAmbientIndicationArea(rootView: KeyguardRootView)
|
||||
fun layoutSettingsPopupMenu(rootView: KeyguardRootView)
|
||||
}
|
||||
|
||||
@@ -28,4 +28,10 @@ abstract class LockscreenLayoutModule {
|
||||
abstract fun bindDefaultLayout(
|
||||
defaultLockscreenLayout: DefaultLockscreenLayout
|
||||
): LockscreenLayout
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindShortcutsBesideUdfpsLockscreenLayout(
|
||||
shortcutsBesideUdfpsLockscreenLayout: ShortcutsBesideUdfpsLockscreenLayout
|
||||
): LockscreenLayout
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* 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.keyguard.ui.view.layout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
|
||||
import androidx.constraintlayout.widget.ConstraintSet.END
|
||||
import androidx.constraintlayout.widget.ConstraintSet.LEFT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
||||
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
|
||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.view.LaunchableLinearLayout
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Positions elements of the lockscreen to the default position.
|
||||
*
|
||||
* This will be the most common use case for phones in portrait mode.
|
||||
*/
|
||||
@SysUISingleton
|
||||
class ShortcutsBesideUdfpsLockscreenLayout
|
||||
@Inject
|
||||
constructor(
|
||||
private val authController: AuthController,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
private val windowManager: WindowManager,
|
||||
private val context: Context,
|
||||
) : LockscreenLayout {
|
||||
override val id: String = SHORTCUTS_BESIDE_UDFPS
|
||||
|
||||
override fun layoutIndicationArea(rootView: KeyguardRootView) {
|
||||
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(indicationArea.id, MATCH_PARENT)
|
||||
constrainHeight(indicationArea.id, WRAP_CONTENT)
|
||||
connect(
|
||||
indicationArea.id,
|
||||
BOTTOM,
|
||||
PARENT_ID,
|
||||
BOTTOM,
|
||||
R.dimen.keyguard_indication_margin_bottom.dp()
|
||||
)
|
||||
connect(indicationArea.id, START, PARENT_ID, START)
|
||||
connect(indicationArea.id, END, PARENT_ID, END)
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutLockIcon(rootView: KeyguardRootView) {
|
||||
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
|
||||
val scaleFactor: Float = authController.scaleFactor
|
||||
val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
|
||||
val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
|
||||
val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
|
||||
val bounds = windowManager.currentWindowMetrics.bounds
|
||||
val widthPixels = bounds.right.toFloat()
|
||||
val heightPixels = bounds.bottom.toFloat()
|
||||
val defaultDensity =
|
||||
DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
|
||||
DisplayMetrics.DENSITY_DEFAULT.toFloat()
|
||||
val lockIconRadiusPx = (defaultDensity * 36).toInt()
|
||||
|
||||
if (isUdfpsSupported) {
|
||||
authController.udfpsLocation?.let { udfpsLocation ->
|
||||
centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
|
||||
}
|
||||
} else {
|
||||
centerLockIcon(
|
||||
Point(
|
||||
(widthPixels / 2).toInt(),
|
||||
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
|
||||
),
|
||||
lockIconRadiusPx * scaleFactor,
|
||||
scaledPadding,
|
||||
rootView
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun centerLockIcon(
|
||||
center: Point,
|
||||
radius: Float,
|
||||
drawablePadding: Int,
|
||||
rootView: KeyguardRootView,
|
||||
) {
|
||||
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
|
||||
val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
|
||||
lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
|
||||
|
||||
val sensorRect =
|
||||
Rect().apply {
|
||||
set(
|
||||
center.x - radius.toInt(),
|
||||
center.y - radius.toInt(),
|
||||
center.x + radius.toInt(),
|
||||
center.y + radius.toInt(),
|
||||
)
|
||||
}
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
|
||||
constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
|
||||
connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
|
||||
connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutShortcuts(rootView: KeyguardRootView) {
|
||||
val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
|
||||
val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
|
||||
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
|
||||
val udfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
|
||||
val width =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
|
||||
val height =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
|
||||
val horizontalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
|
||||
val verticalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
|
||||
val padding =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
|
||||
|
||||
if (!udfpsSupported) {
|
||||
leftShortcut.apply {
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
marginStart = horizontalOffsetMargin
|
||||
bottomMargin = verticalOffsetMargin
|
||||
}
|
||||
setPadding(padding)
|
||||
}
|
||||
|
||||
rightShortcut.apply {
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
marginEnd = horizontalOffsetMargin
|
||||
bottomMargin = verticalOffsetMargin
|
||||
}
|
||||
setPadding(padding)
|
||||
}
|
||||
}
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
if (udfpsSupported) {
|
||||
constrainWidth(leftShortcut.id, width)
|
||||
constrainHeight(leftShortcut.id, height)
|
||||
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(leftShortcut.id, RIGHT, lockIconView.id, LEFT)
|
||||
connect(leftShortcut.id, TOP, lockIconView.id, TOP)
|
||||
connect(leftShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
|
||||
|
||||
constrainWidth(rightShortcut.id, width)
|
||||
constrainHeight(rightShortcut.id, height)
|
||||
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
|
||||
connect(rightShortcut.id, LEFT, lockIconView.id, RIGHT)
|
||||
connect(rightShortcut.id, TOP, lockIconView.id, TOP)
|
||||
connect(rightShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
|
||||
} else {
|
||||
constrainWidth(leftShortcut.id, width)
|
||||
constrainHeight(leftShortcut.id, height)
|
||||
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
|
||||
constrainWidth(rightShortcut.id, width)
|
||||
constrainHeight(rightShortcut.id, height)
|
||||
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
|
||||
connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
}
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
|
||||
val ambientIndicationContainer =
|
||||
rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
|
||||
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
|
||||
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
|
||||
|
||||
if (keyguardUpdateMonitor.isUdfpsSupported) {
|
||||
//constrain below udfps and above indication area
|
||||
constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
|
||||
connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
|
||||
connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
|
||||
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
|
||||
} else {
|
||||
//constrain above lock icon
|
||||
constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
|
||||
connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
|
||||
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
|
||||
}
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
|
||||
val popupMenu =
|
||||
rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
|
||||
val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
|
||||
val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
|
||||
val horizontalOffsetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
|
||||
|
||||
icon.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
|
||||
width =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
|
||||
marginEnd =
|
||||
context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
|
||||
}
|
||||
|
||||
textView.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height = WRAP_CONTENT
|
||||
width = WRAP_CONTENT
|
||||
}
|
||||
|
||||
popupMenu.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
|
||||
marginStart = horizontalOffsetMargin
|
||||
marginEnd = horizontalOffsetMargin
|
||||
}
|
||||
popupMenu.setPadding(
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
|
||||
)
|
||||
|
||||
rootView.getConstraintSet().apply {
|
||||
constrainWidth(popupMenu.id, WRAP_CONTENT)
|
||||
constrainHeight(popupMenu.id, WRAP_CONTENT)
|
||||
constrainMinHeight(
|
||||
popupMenu.id,
|
||||
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
|
||||
)
|
||||
connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
|
||||
connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
|
||||
connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
|
||||
|
||||
applyTo(rootView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.dp(): Int {
|
||||
return context.resources.getDimensionPixelSize(this)
|
||||
}
|
||||
|
||||
private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
|
||||
ConstraintSet().also {
|
||||
it.clone(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.keyguard.ui.viewmodel
|
||||
|
||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class KeyguardAmbientIndicationViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val keyguardInteractor: KeyguardInteractor,
|
||||
private val burnInHelperWrapper: BurnInHelperWrapper,
|
||||
) {
|
||||
|
||||
/** An observable for the x-offset by which the indication area should be translated. */
|
||||
val indicationAreaTranslationX: Flow<Float> =
|
||||
keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||
|
||||
/** Returns an observable for the y-offset by which the indication area should be translated. */
|
||||
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
|
||||
return keyguardInteractor.dozeAmount
|
||||
.map { dozeAmount ->
|
||||
dozeAmount *
|
||||
(burnInHelperWrapper.burnInOffset(
|
||||
/* amplitude = */ defaultBurnInOffset * 2,
|
||||
/* xAxis= */ false,
|
||||
) - defaultBurnInOffset)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.systemui.keyguard.ui.viewmodel
|
||||
|
||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import javax.inject.Inject
|
||||
@@ -33,6 +35,8 @@ constructor(
|
||||
bottomAreaInteractor: KeyguardBottomAreaInteractor,
|
||||
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
|
||||
private val burnInHelperWrapper: BurnInHelperWrapper,
|
||||
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
|
||||
private val featureFlags: FeatureFlags,
|
||||
) {
|
||||
|
||||
/** Notifies when a new configuration is set */
|
||||
@@ -43,15 +47,28 @@ constructor(
|
||||
|
||||
/** An observable for whether the indication area should be padded. */
|
||||
val isIndicationAreaPadded: Flow<Boolean> =
|
||||
combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
|
||||
startButtonModel,
|
||||
endButtonModel ->
|
||||
startButtonModel.isVisible || endButtonModel.isVisible
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
|
||||
startButtonModel,
|
||||
endButtonModel ->
|
||||
startButtonModel.isVisible || endButtonModel.isVisible
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
/** An observable for the x-offset by which the indication area should be translated. */
|
||||
val indicationAreaTranslationX: Flow<Float> =
|
||||
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||
} else {
|
||||
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||
}
|
||||
|
||||
/** Returns an observable for the y-offset by which the indication area should be translated. */
|
||||
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.keyguard.ui.viewmodel
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
||||
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class KeyguardQuickAffordancesCombinedViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
|
||||
private val keyguardInteractor: KeyguardInteractor,
|
||||
) {
|
||||
|
||||
/**
|
||||
* ID of the slot that's currently selected in the preview that renders exclusively in the
|
||||
* wallpaper picker application. This is ignored for the actual, real lock screen experience.
|
||||
*/
|
||||
private val selectedPreviewSlotId =
|
||||
MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
|
||||
|
||||
/**
|
||||
* Whether quick affordances are "opaque enough" to be considered visible to and interactive by
|
||||
* the user. If they are not interactive, user input should not be allowed on them.
|
||||
*
|
||||
* Note that there is a margin of error, where we allow very, very slightly transparent views to
|
||||
* be considered "fully opaque" for the purpose of being interactive. This is to accommodate the
|
||||
* error margin of floating point arithmetic.
|
||||
*
|
||||
* A view that is visible but with an alpha of less than our threshold either means it's not
|
||||
* fully done fading in or is fading/faded out. Either way, it should not be
|
||||
* interactive/clickable unless "fully opaque" to avoid issues like in b/241830987.
|
||||
*/
|
||||
private val areQuickAffordancesFullyOpaque: Flow<Boolean> =
|
||||
keyguardInteractor.keyguardAlpha
|
||||
.map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
|
||||
.distinctUntilChanged()
|
||||
|
||||
/** An observable for the view-model of the "start button" quick affordance. */
|
||||
val startButton: Flow<KeyguardQuickAffordanceViewModel> =
|
||||
button(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
|
||||
/** An observable for the view-model of the "end button" quick affordance. */
|
||||
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
|
||||
button(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
|
||||
/**
|
||||
* Notifies that a slot with the given ID has been selected in the preview experience that is
|
||||
* rendering in the wallpaper picker. This is ignored for the real lock screen experience.
|
||||
*
|
||||
* @see [KeyguardRootViewModel.enablePreviewMode]
|
||||
*/
|
||||
fun onPreviewSlotSelected(slotId: String) {
|
||||
selectedPreviewSlotId.value = slotId
|
||||
}
|
||||
|
||||
private fun button(
|
||||
position: KeyguardQuickAffordancePosition
|
||||
): Flow<KeyguardQuickAffordanceViewModel> {
|
||||
return keyguardInteractor.previewMode.flatMapLatest { previewMode ->
|
||||
combine(
|
||||
if (previewMode.isInPreviewMode) {
|
||||
quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
|
||||
} else {
|
||||
quickAffordanceInteractor.quickAffordance(position = position)
|
||||
},
|
||||
keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
|
||||
areQuickAffordancesFullyOpaque,
|
||||
selectedPreviewSlotId,
|
||||
quickAffordanceInteractor.useLongPress(),
|
||||
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
|
||||
val slotId = position.toSlotId()
|
||||
val isSelected = selectedPreviewSlotId == slotId
|
||||
model.toViewModel(
|
||||
animateReveal = !previewMode.isInPreviewMode && animateReveal,
|
||||
isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
|
||||
isSelected =
|
||||
previewMode.isInPreviewMode &&
|
||||
previewMode.shouldHighlightSelectedAffordance &&
|
||||
isSelected,
|
||||
isDimmed =
|
||||
previewMode.isInPreviewMode &&
|
||||
previewMode.shouldHighlightSelectedAffordance &&
|
||||
!isSelected,
|
||||
forceInactive = previewMode.isInPreviewMode,
|
||||
slotId = slotId,
|
||||
useLongPress = useLongPress,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun KeyguardQuickAffordanceModel.toViewModel(
|
||||
animateReveal: Boolean,
|
||||
isClickable: Boolean,
|
||||
isSelected: Boolean,
|
||||
isDimmed: Boolean,
|
||||
forceInactive: Boolean,
|
||||
slotId: String,
|
||||
useLongPress: Boolean,
|
||||
): KeyguardQuickAffordanceViewModel {
|
||||
return when (this) {
|
||||
is KeyguardQuickAffordanceModel.Visible ->
|
||||
KeyguardQuickAffordanceViewModel(
|
||||
configKey = configKey,
|
||||
isVisible = true,
|
||||
animateReveal = animateReveal,
|
||||
icon = icon,
|
||||
onClicked = { parameters ->
|
||||
quickAffordanceInteractor.onQuickAffordanceTriggered(
|
||||
configKey = parameters.configKey,
|
||||
expandable = parameters.expandable,
|
||||
slotId = parameters.slotId,
|
||||
)
|
||||
},
|
||||
isClickable = isClickable,
|
||||
isActivated = !forceInactive && activationState is ActivationState.Active,
|
||||
isSelected = isSelected,
|
||||
useLongPress = useLongPress,
|
||||
isDimmed = isDimmed,
|
||||
slotId = slotId,
|
||||
)
|
||||
is KeyguardQuickAffordanceModel.Hidden ->
|
||||
KeyguardQuickAffordanceViewModel(
|
||||
slotId = slotId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// We select a value that's less than 1.0 because we want floating point math precision to
|
||||
// not be a factor in determining whether the affordance UI is fully opaque. The number we
|
||||
// choose needs to be close enough 1.0 such that the user can't easily tell the difference
|
||||
// between the UI with an alpha at the threshold and when the alpha is 1.0. At the same
|
||||
// time, we don't want the number to be too close to 1.0 such that there is a chance that we
|
||||
// never treat the affordance UI as "fully opaque" as that would risk making it forever not
|
||||
// clickable.
|
||||
@VisibleForTesting
|
||||
const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.keyguard.ui.viewmodel
|
||||
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class KeyguardRootViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val keyguardInteractor: KeyguardInteractor,
|
||||
private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
|
||||
)
|
||||
{
|
||||
/** Represents the current state of the KeyguardRootView visibility */
|
||||
val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
|
||||
keyguardInteractor.keyguardRootViewVisibilityState
|
||||
|
||||
/** An observable for the alpha level for the entire keyguard root view. */
|
||||
val alpha: Flow<Float> =
|
||||
keyguardInteractor.previewMode.flatMapLatest {
|
||||
if (it.isInPreviewMode) {
|
||||
flowOf(1f)
|
||||
} else {
|
||||
keyguardInteractor.keyguardAlpha.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
|
||||
* the lock screen preview in wallpaper picker / settings and not the real experience on the
|
||||
* 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?,
|
||||
shouldHighlightSelectedAffordance: Boolean,
|
||||
) {
|
||||
keyguardInteractor.previewMode.value =
|
||||
KeyguardInteractor.PreviewMode(
|
||||
isInPreviewMode = true,
|
||||
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||
)
|
||||
keyguardQuickAffordancesCombinedViewModel.onPreviewSlotSelected(
|
||||
initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -138,6 +138,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState;
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep;
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel;
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||
@@ -348,6 +349,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
|
||||
private final NotificationGutsManager mGutsManager;
|
||||
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
|
||||
private final KeyguardRootView mKeyguardRootView;
|
||||
private final QuickSettingsController mQsController;
|
||||
private final TouchHandler mTouchHandler = new TouchHandler();
|
||||
|
||||
@@ -363,6 +365,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
private boolean mTracking;
|
||||
private boolean mIsTrackingExpansionFromStatusBar;
|
||||
private boolean mHintAnimationRunning;
|
||||
@Deprecated
|
||||
private KeyguardBottomAreaView mKeyguardBottomArea;
|
||||
private boolean mExpanding;
|
||||
private boolean mSplitShadeEnabled;
|
||||
@@ -739,7 +742,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
KeyguardInteractor keyguardInteractor,
|
||||
ActivityStarter activityStarter,
|
||||
KeyguardViewConfigurator keyguardViewConfigurator,
|
||||
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
|
||||
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
|
||||
KeyguardRootView keyguardRootView) {
|
||||
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
|
||||
@Override
|
||||
public void onKeyguardFadingAwayChanged() {
|
||||
@@ -939,6 +943,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
}
|
||||
});
|
||||
mAlternateBouncerInteractor = alternateBouncerInteractor;
|
||||
mKeyguardRootView = keyguardRootView;
|
||||
dumpManager.registerDumpable(this);
|
||||
}
|
||||
|
||||
@@ -1029,7 +1034,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
|
||||
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
|
||||
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
|
||||
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
|
||||
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
|
||||
}
|
||||
|
||||
initBottomArea();
|
||||
|
||||
@@ -1311,14 +1318,17 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
|
||||
keyguardUserSwitcherView);
|
||||
|
||||
// Update keyguard bottom area
|
||||
int index = mView.indexOfChild(mKeyguardBottomArea);
|
||||
mView.removeView(mKeyguardBottomArea);
|
||||
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
|
||||
setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
|
||||
mKeyguardBottomArea.initFrom(oldBottomArea);
|
||||
mView.addView(mKeyguardBottomArea, index);
|
||||
initBottomArea();
|
||||
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
// Update keyguard bottom area
|
||||
int index = mView.indexOfChild(mKeyguardBottomArea);
|
||||
mView.removeView(mKeyguardBottomArea);
|
||||
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
|
||||
setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
|
||||
mKeyguardBottomArea.initFrom(oldBottomArea);
|
||||
mView.addView(mKeyguardBottomArea, index);
|
||||
|
||||
initBottomArea();
|
||||
}
|
||||
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
|
||||
mStatusBarStateController.getInterpolatedDozeAmount());
|
||||
|
||||
@@ -1341,7 +1351,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
false,
|
||||
mBarState);
|
||||
}
|
||||
setKeyguardBottomAreaVisibility(mBarState, false);
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
setKeyguardVisibility(mBarState, false);
|
||||
} else {
|
||||
setKeyguardBottomAreaVisibility(mBarState, false);
|
||||
}
|
||||
|
||||
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
|
||||
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
|
||||
@@ -1352,7 +1367,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
}
|
||||
|
||||
private void initBottomArea() {
|
||||
mKeyguardBottomArea.init(
|
||||
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardBottomArea.init(
|
||||
mKeyguardBottomAreaViewModel,
|
||||
mFalsingManager,
|
||||
mLockIconViewController,
|
||||
@@ -1361,8 +1377,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mVibratorHelper,
|
||||
mActivityStarter);
|
||||
|
||||
// Rebind (for now), as a new bottom area and indication area may have been created
|
||||
mKeyguardViewConfigurator.bindIndicationArea(mKeyguardBottomArea);
|
||||
// Rebind (for now), as a new bottom area and indication area may have been created
|
||||
mKeyguardViewConfigurator.bindIndicationArea();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -1398,6 +1415,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
|
||||
mKeyguardBottomArea = keyguardBottomArea;
|
||||
}
|
||||
@@ -1521,8 +1539,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mClockPositionAlgorithm.run(mClockPositionResult);
|
||||
mKeyguardStatusViewController.setLockscreenClockY(
|
||||
mClockPositionAlgorithm.getExpandedPreferredClockY());
|
||||
mKeyguardBottomAreaInteractor.setClockPosition(
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardInteractor.setClockPosition(
|
||||
mClockPositionResult.clockX, mClockPositionResult.clockY);
|
||||
} else {
|
||||
mKeyguardBottomAreaInteractor.setClockPosition(
|
||||
mClockPositionResult.clockX, mClockPositionResult.clockY);
|
||||
}
|
||||
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
|
||||
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
|
||||
mKeyguardStatusViewController.updatePosition(
|
||||
@@ -2169,6 +2192,15 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
|
||||
mKeyguardInteractor.setKeyguardRootVisibility(
|
||||
statusBarState,
|
||||
goingToFullShade,
|
||||
mIsOcclusionTransitionRunning
|
||||
);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
|
||||
mKeyguardBottomArea.animate().cancel();
|
||||
if (goingToFullShade) {
|
||||
@@ -2177,8 +2209,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
|
||||
Interpolators.ALPHA_OUT).withEndAction(
|
||||
mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
|
||||
} else if (statusBarState == KEYGUARD
|
||||
|| statusBarState == StatusBarState.SHADE_LOCKED) {
|
||||
} else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) {
|
||||
mKeyguardBottomArea.setVisibility(View.VISIBLE);
|
||||
if (!mIsOcclusionTransitionRunning) {
|
||||
mKeyguardBottomArea.setAlpha(1f);
|
||||
@@ -2514,7 +2545,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
getExpandedFraction());
|
||||
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
|
||||
alpha *= mBottomAreaShadeAlpha;
|
||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardInteractor.setAlpha(alpha);
|
||||
} else {
|
||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||
}
|
||||
mLockIconViewController.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@@ -2773,7 +2808,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
}
|
||||
|
||||
private void updateDozingVisibilities(boolean animate) {
|
||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardInteractor.setAnimateDozingTransitions(animate);
|
||||
} else {
|
||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||
}
|
||||
if (!mDozing && animate) {
|
||||
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
|
||||
}
|
||||
@@ -2977,7 +3016,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mDozing = dozing;
|
||||
// TODO (b/) make listeners for this
|
||||
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
|
||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardInteractor.setAnimateDozingTransitions(animate);
|
||||
} else {
|
||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||
}
|
||||
mKeyguardStatusBarViewController.setDozing(mDozing);
|
||||
mQsController.setDozing(mDozing);
|
||||
|
||||
@@ -3898,20 +3941,37 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
animator.start();
|
||||
setAnimator(animator);
|
||||
|
||||
final List<ViewPropertyAnimator> indicationAnimators =
|
||||
mKeyguardBottomArea.getIndicationAreaAnimators();
|
||||
for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
|
||||
indicationAreaAnimator
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
final ViewPropertyAnimator mKeyguardRootViewAnimator = mKeyguardRootView.animate();
|
||||
mKeyguardRootViewAnimator
|
||||
.translationY(-mHintDistance)
|
||||
.setDuration(250)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.withEndAction(() -> indicationAreaAnimator
|
||||
.withEndAction(() -> mKeyguardRootViewAnimator
|
||||
.translationY(0)
|
||||
.setDuration(450)
|
||||
.setInterpolator(mBounceInterpolator)
|
||||
.start())
|
||||
.start();
|
||||
} else {
|
||||
final List<ViewPropertyAnimator> indicationAnimators =
|
||||
mKeyguardBottomArea.getIndicationAreaAnimators();
|
||||
|
||||
for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
|
||||
indicationAreaAnimator
|
||||
.translationY(-mHintDistance)
|
||||
.setDuration(250)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.withEndAction(() -> indicationAreaAnimator
|
||||
.translationY(0)
|
||||
.setDuration(450)
|
||||
.setInterpolator(mBounceInterpolator)
|
||||
.start())
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setAnimator(ValueAnimator animator) {
|
||||
@@ -4352,7 +4412,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
goingToFullShade,
|
||||
mBarState);
|
||||
|
||||
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
setKeyguardVisibility(statusBarState, goingToFullShade);
|
||||
} else {
|
||||
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
|
||||
}
|
||||
|
||||
// TODO: maybe add a listener for barstate
|
||||
mBarState = statusBarState;
|
||||
@@ -4589,7 +4653,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
||||
mKeyguardStatusViewController.setAlpha(alpha);
|
||||
stackScroller.setAlpha(alpha);
|
||||
|
||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||
mKeyguardInteractor.setAlpha(alpha);
|
||||
} else {
|
||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||
}
|
||||
mLockIconViewController.setAlpha(alpha);
|
||||
|
||||
if (mKeyguardQsUserSwitchController != null) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.VibratorHelper
|
||||
* elements. A secondary concern is the interaction of the quick affordance elements with the
|
||||
* indication area between them, though the indication area is primarily controlled elsewhere.
|
||||
*/
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
class KeyguardBottomAreaView
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
@@ -53,6 +54,7 @@ constructor(
|
||||
defStyleRes,
|
||||
) {
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
interface MessageDisplayer {
|
||||
fun display(@StringRes stringResourceId: Int)
|
||||
}
|
||||
@@ -62,6 +64,7 @@ constructor(
|
||||
private var lockIconViewController: LockIconViewController? = null
|
||||
|
||||
/** Initializes the view. */
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
fun init(
|
||||
viewModel: KeyguardBottomAreaViewModel,
|
||||
falsingManager: FalsingManager? = null,
|
||||
@@ -88,6 +91,7 @@ constructor(
|
||||
* Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
|
||||
* [KeyguardBottomAreaView]
|
||||
*/
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
|
||||
// if it exists, continue to use the original ambient indication container
|
||||
// instead of the newly inflated one
|
||||
@@ -122,9 +126,11 @@ constructor(
|
||||
}
|
||||
|
||||
/** Returns a list of animators to use to animate the indication areas. */
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
val indicationAreaAnimators: List<ViewPropertyAnimator>
|
||||
get() = checkNotNull(binding).getIndicationAreaAnimators()
|
||||
|
||||
|
||||
override fun hasOverlappingRendering(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -292,24 +292,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun quickAffordance_hiddenWhenQuickSettingsIsVisible() =
|
||||
testScope.runTest {
|
||||
repository.setQuickSettingsVisible(true)
|
||||
quickAccessWallet.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
)
|
||||
)
|
||||
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
)
|
||||
|
||||
assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun quickAffordance_hiddenWhenUserIsInLockdownMode() =
|
||||
testScope.runTest {
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||
import com.android.systemui.monet.utils.ArgbSubject.assertThat
|
||||
import org.junit.Before
|
||||
@@ -42,9 +44,10 @@ class DefaultLockscreenLayoutTest : SysuiTestCase() {
|
||||
private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
|
||||
private lateinit var rootView: KeyguardRootView
|
||||
@Mock private lateinit var authController: AuthController
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
|
||||
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
|
||||
@@ -45,6 +46,8 @@ import org.mockito.MockitoAnnotations
|
||||
class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
|
||||
@Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
|
||||
@Mock private lateinit var featureFlags: FeatureFlags
|
||||
|
||||
private lateinit var underTest: KeyguardIndicationAreaViewModel
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
@@ -83,6 +86,8 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
|
||||
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
|
||||
keyguardBottomAreaViewModel = bottomAreaViewModel,
|
||||
burnInHelperWrapper = burnInHelperWrapper,
|
||||
shortcutsCombinedViewModel = shortcutsCombinedViewModel,
|
||||
featureFlags = featureFlags,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
/*
|
||||
* 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.keyguard.ui.viewmodel
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.Intent
|
||||
import android.os.UserHandle
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.DialogLaunchAnimator
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.dock.DockManagerFake
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
|
||||
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.settings.UserFileManager
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.util.FakeSharedPreferences
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var activityStarter: ActivityStarter
|
||||
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
|
||||
@Mock private lateinit var expandable: Expandable
|
||||
@Mock private lateinit var userTracker: UserTracker
|
||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
|
||||
@Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
|
||||
|
||||
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
|
||||
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||
private lateinit var dockManager: DockManagerFake
|
||||
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
|
||||
private lateinit var keyguardInteractor: KeyguardInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
|
||||
overrideResource(
|
||||
R.array.config_keyguardQuickAffordanceDefaults,
|
||||
arrayOf(
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
|
||||
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
|
||||
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
||||
)
|
||||
)
|
||||
|
||||
homeControlsQuickAffordanceConfig =
|
||||
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
|
||||
quickAccessWalletAffordanceConfig =
|
||||
FakeKeyguardQuickAffordanceConfig(
|
||||
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
||||
)
|
||||
qrCodeScannerAffordanceConfig =
|
||||
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
||||
dockManager = DockManagerFake()
|
||||
biometricSettingsRepository = FakeBiometricSettingsRepository()
|
||||
val featureFlags =
|
||||
FakeFeatureFlags().apply {
|
||||
set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
|
||||
set(Flags.FACE_AUTH_REFACTOR, true)
|
||||
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
|
||||
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
|
||||
}
|
||||
|
||||
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
|
||||
keyguardInteractor = withDeps.keyguardInteractor
|
||||
repository = withDeps.repository
|
||||
|
||||
whenever(userTracker.userHandle).thenReturn(mock())
|
||||
whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt()))
|
||||
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(testDispatcher)
|
||||
|
||||
val localUserSelectionManager =
|
||||
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||
context = context,
|
||||
userFileManager =
|
||||
mock<UserFileManager>().apply {
|
||||
whenever(
|
||||
getSharedPreferences(
|
||||
ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyInt(),
|
||||
ArgumentMatchers.anyInt(),
|
||||
)
|
||||
)
|
||||
.thenReturn(FakeSharedPreferences())
|
||||
},
|
||||
userTracker = userTracker,
|
||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||
)
|
||||
val remoteUserSelectionManager =
|
||||
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||
scope = testScope.backgroundScope,
|
||||
userTracker = userTracker,
|
||||
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||
userHandle = UserHandle.SYSTEM,
|
||||
)
|
||||
val quickAffordanceRepository =
|
||||
KeyguardQuickAffordanceRepository(
|
||||
appContext = context,
|
||||
scope = testScope.backgroundScope,
|
||||
localUserSelectionManager = localUserSelectionManager,
|
||||
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||
userTracker = userTracker,
|
||||
legacySettingSyncer =
|
||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||
scope = testScope.backgroundScope,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
secureSettings = FakeSettings(),
|
||||
selectionsManager = localUserSelectionManager,
|
||||
),
|
||||
configs =
|
||||
setOf(
|
||||
homeControlsQuickAffordanceConfig,
|
||||
quickAccessWalletAffordanceConfig,
|
||||
qrCodeScannerAffordanceConfig,
|
||||
),
|
||||
dumpManager = mock(),
|
||||
userHandle = UserHandle.SYSTEM,
|
||||
)
|
||||
|
||||
underTest = KeyguardQuickAffordancesCombinedViewModel(
|
||||
quickAffordanceInteractor =
|
||||
KeyguardQuickAffordanceInteractor(
|
||||
keyguardInteractor = keyguardInteractor,
|
||||
lockPatternUtils = lockPatternUtils,
|
||||
keyguardStateController = keyguardStateController,
|
||||
userTracker = userTracker,
|
||||
activityStarter = activityStarter,
|
||||
featureFlags = featureFlags,
|
||||
repository = { quickAffordanceRepository },
|
||||
launchAnimator = launchAnimator,
|
||||
logger = logger,
|
||||
devicePolicyManager = devicePolicyManager,
|
||||
dockManager = dockManager,
|
||||
biometricSettingsRepository = biometricSettingsRepository,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
appContext = mContext,
|
||||
),
|
||||
keyguardInteractor = keyguardInteractor
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startButton_present_visibleModel_startsActivityOnClick() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
|
||||
testScope.runTest {
|
||||
whenever(
|
||||
devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
|
||||
).thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest by collectLastValue(underTest.startButton)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
),
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
|
||||
testScope.runTest {
|
||||
underTest.onPreviewSlotSelected(
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
)
|
||||
keyguardInteractor.previewMode.value =
|
||||
KeyguardInteractor.PreviewMode(
|
||||
isInPreviewMode = true,
|
||||
shouldHighlightSelectedAffordance = true,
|
||||
)
|
||||
|
||||
repository.setKeyguardShowing(false)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val icon: Icon = mock()
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
),
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
isActivated = false,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
isSelected = true,
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
),
|
||||
configKey = configKey,
|
||||
)
|
||||
Truth.assertThat(latest()?.isSelected).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
|
||||
testScope.runTest {
|
||||
underTest.onPreviewSlotSelected(
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
)
|
||||
keyguardInteractor.previewMode.value =
|
||||
KeyguardInteractor.PreviewMode(
|
||||
isInPreviewMode = true,
|
||||
shouldHighlightSelectedAffordance = true,
|
||||
)
|
||||
|
||||
repository.setKeyguardShowing(false)
|
||||
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"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
|
||||
),
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = endButton(),
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
isActivated = false,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
isDimmed = true,
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
|
||||
),
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endButton_present_visibleModel_doNothingOnClick() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.endButton)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent =
|
||||
null, // This will cause it to tell the system that the click was handled.
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startButton_notPresent_modelIsHidden() =
|
||||
testScope.runTest {
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateButtonReveal() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
val value = collectLastValue(underTest.startButton.map { it.animateReveal })
|
||||
|
||||
Truth.assertThat(value()).isFalse()
|
||||
repository.setAnimateDozingTransitions(true)
|
||||
Truth.assertThat(value()).isTrue()
|
||||
repository.setAnimateDozingTransitions(false)
|
||||
Truth.assertThat(value()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClickable_trueWhenAlphaAtThreshold() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
repository.setKeyguardAlpha(
|
||||
KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClickable_trueWhenAlphaAboveThreshold() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setKeyguardAlpha(
|
||||
min(
|
||||
1f,
|
||||
KeyguardQuickAffordancesCombinedViewModel
|
||||
.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f
|
||||
),
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClickable_falseWhenAlphaBelowThreshold() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setKeyguardAlpha(
|
||||
max(
|
||||
0f,
|
||||
KeyguardQuickAffordancesCombinedViewModel
|
||||
.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f
|
||||
),
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isClickable_falseWhenAlphaAtZero() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setKeyguardAlpha(0f)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun setUpQuickAffordanceModel(
|
||||
position: KeyguardQuickAffordancePosition,
|
||||
testConfig: TestConfig,
|
||||
): String {
|
||||
val config =
|
||||
when (position) {
|
||||
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
|
||||
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
|
||||
}
|
||||
|
||||
val lockScreenState =
|
||||
if (testConfig.isVisible) {
|
||||
if (testConfig.intent != null) {
|
||||
config.onTriggeredResult =
|
||||
KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
|
||||
intent = testConfig.intent,
|
||||
canShowWhileLocked = testConfig.canShowWhileLocked,
|
||||
)
|
||||
}
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
|
||||
activationState =
|
||||
when (testConfig.isActivated) {
|
||||
true -> ActivationState.Active
|
||||
false -> ActivationState.Inactive
|
||||
}
|
||||
)
|
||||
} else {
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
|
||||
}
|
||||
config.setState(lockScreenState)
|
||||
return "${position.toSlotId()}::${config.key}"
|
||||
}
|
||||
|
||||
private fun assertQuickAffordanceViewModel(
|
||||
viewModel: KeyguardQuickAffordanceViewModel?,
|
||||
testConfig: TestConfig,
|
||||
configKey: String,
|
||||
) {
|
||||
checkNotNull(viewModel)
|
||||
Truth.assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
|
||||
Truth.assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
|
||||
Truth.assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
|
||||
Truth.assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
|
||||
Truth.assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
|
||||
Truth.assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
|
||||
if (testConfig.isVisible) {
|
||||
Truth.assertThat(viewModel.icon).isEqualTo(testConfig.icon)
|
||||
viewModel.onClicked.invoke(
|
||||
KeyguardQuickAffordanceViewModel.OnClickedParameters(
|
||||
configKey = configKey,
|
||||
expandable = expandable,
|
||||
slotId = viewModel.slotId,
|
||||
)
|
||||
)
|
||||
if (testConfig.intent != null) {
|
||||
Truth.assertThat(
|
||||
Mockito.mockingDetails(activityStarter).invocations
|
||||
).hasSize(1)
|
||||
} else {
|
||||
Mockito.verifyZeroInteractions(activityStarter)
|
||||
}
|
||||
} else {
|
||||
Truth.assertThat(viewModel.isVisible).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
private data class TestConfig(
|
||||
val isVisible: Boolean,
|
||||
val isClickable: Boolean = false,
|
||||
val isActivated: Boolean = false,
|
||||
val icon: Icon? = null,
|
||||
val canShowWhileLocked: Boolean = false,
|
||||
val intent: Intent? = null,
|
||||
val isSelected: Boolean = false,
|
||||
val isDimmed: Boolean = false,
|
||||
val slotId: String = ""
|
||||
) {
|
||||
init {
|
||||
check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.keyguard.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
|
||||
import com.google.common.truth.Truth
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class KeyguardRootViewModelTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: KeyguardRootViewModel
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
private lateinit var keyguardInteractor: KeyguardInteractor
|
||||
@Mock private lateinit var keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(testDispatcher)
|
||||
|
||||
val featureFlags =
|
||||
FakeFeatureFlags().apply {
|
||||
set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
|
||||
set(Flags.FACE_AUTH_REFACTOR, true)
|
||||
}
|
||||
|
||||
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
|
||||
keyguardInteractor = withDeps.keyguardInteractor
|
||||
repository = withDeps.repository
|
||||
|
||||
underTest = KeyguardRootViewModel(
|
||||
keyguardInteractor,
|
||||
keyguardQuickAffordancesCombinedViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alpha() =
|
||||
testScope.runTest {
|
||||
val value = collectLastValue(underTest.alpha)
|
||||
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
repository.setKeyguardAlpha(0.1f)
|
||||
Truth.assertThat(value()).isEqualTo(0.1f)
|
||||
repository.setKeyguardAlpha(0.5f)
|
||||
Truth.assertThat(value()).isEqualTo(0.5f)
|
||||
repository.setKeyguardAlpha(0.2f)
|
||||
Truth.assertThat(value()).isEqualTo(0.2f)
|
||||
repository.setKeyguardAlpha(0f)
|
||||
Truth.assertThat(value()).isEqualTo(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alpha_inPreviewMode_doesNotChange() =
|
||||
testScope.runTest {
|
||||
val value = collectLastValue(underTest.alpha)
|
||||
underTest.enablePreviewMode(
|
||||
initiallySelectedSlotId = null,
|
||||
shouldHighlightSelectedAffordance = false,
|
||||
)
|
||||
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
repository.setKeyguardAlpha(0.1f)
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
repository.setKeyguardAlpha(0.5f)
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
repository.setKeyguardAlpha(0.2f)
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
repository.setKeyguardAlpha(0f)
|
||||
Truth.assertThat(value()).isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
|
||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||
@@ -314,6 +315,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
|
||||
@Mock private ShadeInteractor mShadeInteractor;
|
||||
@Mock private JavaAdapter mJavaAdapter;
|
||||
@Mock private CastController mCastController;
|
||||
@Mock private KeyguardRootView mKeyguardRootView;
|
||||
|
||||
protected final int mMaxUdfpsBurnInOffsetY = 5;
|
||||
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
|
||||
@@ -618,7 +620,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
|
||||
mKeyguardInteractor,
|
||||
mActivityStarter,
|
||||
mKeyguardViewConfigurator,
|
||||
mKeyguardFaceAuthInteractor);
|
||||
mKeyguardFaceAuthInteractor,
|
||||
mKeyguardRootView);
|
||||
mNotificationPanelViewController.initDependencies(
|
||||
mCentralSurfaces,
|
||||
null,
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.android.systemui.common.shared.model.Position
|
||||
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
|
||||
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
|
||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
|
||||
import com.android.systemui.keyguard.shared.model.ScreenModel
|
||||
import com.android.systemui.keyguard.shared.model.ScreenState
|
||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||
@@ -115,6 +116,20 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
private val _isQuickSettingsVisible = MutableStateFlow(false)
|
||||
override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
|
||||
|
||||
private val _keyguardAlpha = MutableStateFlow(1f)
|
||||
override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
|
||||
|
||||
private val _keyguardRootViewVisibility =
|
||||
MutableStateFlow(
|
||||
KeyguardRootViewVisibilityState(
|
||||
0,
|
||||
goingToFullShade = false,
|
||||
occlusionTransitionRunning = false
|
||||
)
|
||||
)
|
||||
override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
|
||||
_keyguardRootViewVisibility.asStateFlow()
|
||||
|
||||
override fun setQuickSettingsVisible(isVisible: Boolean) {
|
||||
_isQuickSettingsVisible.value = isVisible
|
||||
}
|
||||
@@ -132,6 +147,8 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
_animateBottomAreaDozingTransitions.tryEmit(animate)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Deprecated as part of b/278057014")
|
||||
override fun setBottomAreaAlpha(alpha: Float) {
|
||||
_bottomAreaAlpha.value = alpha
|
||||
}
|
||||
@@ -223,4 +240,18 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
override fun isUdfpsSupported(): Boolean {
|
||||
return _isUdfpsSupported.value
|
||||
}
|
||||
|
||||
override fun setKeyguardAlpha(alpha: Float) {
|
||||
_keyguardAlpha.value = alpha
|
||||
}
|
||||
|
||||
override fun setKeyguardVisibility(
|
||||
statusBarState: Int,
|
||||
goingToFullShade: Boolean,
|
||||
occlusionTransitionRunning: Boolean
|
||||
) {
|
||||
_keyguardRootViewVisibility.value = KeyguardRootViewVisibilityState(
|
||||
statusBarState, goingToFullShade, occlusionTransitionRunning
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user