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_width="match_parent"
|
||||||
android:layout_height="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. -->
|
<!-- Root for all keyguard content. It was previously located within the shade. -->
|
||||||
<com.android.systemui.keyguard.ui.view.KeyguardRootView
|
<com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||||
android:id="@id/keyguard_root_view"
|
android:id="@id/keyguard_root_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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
|
<!-- Shared container for the notification stack. Can be positioned by either
|
||||||
the keyguard_root_view or notification_panel -->
|
the keyguard_root_view or notification_panel -->
|
||||||
<com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
<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_radius">24dp</dimen>
|
||||||
<dimen name="keyguard_affordance_fixed_padding">12dp</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. -->
|
<!-- Amount the button should shake when it's not long-pressed for long enough. -->
|
||||||
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
|
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard
|
package com.android.systemui.keyguard
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.android.systemui.CoreStartable
|
import com.android.systemui.CoreStartable
|
||||||
@@ -24,18 +25,29 @@ import com.android.systemui.R
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.flags.FeatureFlags
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
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.KeyguardIndicationAreaBinder
|
||||||
|
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
|
||||||
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
|
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.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.KeyguardLayoutManager
|
||||||
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
|
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
|
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.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
|
||||||
import com.android.systemui.shade.NotificationShadeWindowView
|
import com.android.systemui.shade.NotificationShadeWindowView
|
||||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||||
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
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.viewbinder.SharedNotificationContainerBinder
|
||||||
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
|
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 com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.DisposableHandle
|
import kotlinx.coroutines.DisposableHandle
|
||||||
@@ -49,26 +61,44 @@ class KeyguardViewConfigurator
|
|||||||
constructor(
|
constructor(
|
||||||
private val keyguardRootView: KeyguardRootView,
|
private val keyguardRootView: KeyguardRootView,
|
||||||
private val sharedNotificationContainer: SharedNotificationContainer,
|
private val sharedNotificationContainer: SharedNotificationContainer,
|
||||||
|
private val keyguardRootViewModel: KeyguardRootViewModel,
|
||||||
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
|
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
|
||||||
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
|
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
|
||||||
|
private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
|
||||||
private val notificationShadeWindowView: NotificationShadeWindowView,
|
private val notificationShadeWindowView: NotificationShadeWindowView,
|
||||||
private val featureFlags: FeatureFlags,
|
private val featureFlags: FeatureFlags,
|
||||||
private val indicationController: KeyguardIndicationController,
|
private val indicationController: KeyguardIndicationController,
|
||||||
private val keyguardLayoutManager: KeyguardLayoutManager,
|
private val keyguardLayoutManager: KeyguardLayoutManager,
|
||||||
private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
|
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 occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
|
||||||
private val chipbarCoordinator: ChipbarCoordinator,
|
private val chipbarCoordinator: ChipbarCoordinator,
|
||||||
) : CoreStartable {
|
) : CoreStartable {
|
||||||
|
|
||||||
|
private var rootViewHandle: DisposableHandle? = null
|
||||||
private var indicationAreaHandle: 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() {
|
override fun start() {
|
||||||
bindKeyguardRootView()
|
bindKeyguardRootView()
|
||||||
val notificationPanel =
|
val notificationPanel =
|
||||||
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
|
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
|
||||||
bindIndicationArea(notificationPanel)
|
unbindKeyguardBottomArea(notificationPanel)
|
||||||
|
bindIndicationArea()
|
||||||
bindLockIconView(notificationPanel)
|
bindLockIconView(notificationPanel)
|
||||||
setupNotificationStackScrollLayout(notificationPanel)
|
setupNotificationStackScrollLayout(notificationPanel)
|
||||||
|
bindLeftShortcut()
|
||||||
|
bindRightShortcut()
|
||||||
|
bindAmbientIndicationArea()
|
||||||
|
bindSettingsPopupMenu()
|
||||||
|
|
||||||
keyguardLayoutManager.layoutViews()
|
keyguardLayoutManager.layoutViews()
|
||||||
keyguardLayoutManagerCommandListener.start()
|
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()
|
indicationAreaHandle?.dispose()
|
||||||
|
|
||||||
// At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
|
// At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
|
||||||
// Disable one of them
|
// Disable one of them
|
||||||
if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
|
if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
|
||||||
legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
|
|
||||||
legacyParent.removeView(it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
|
keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
|
||||||
keyguardRootView.removeView(it)
|
keyguardRootView.removeView(it)
|
||||||
}
|
}
|
||||||
@@ -109,7 +144,21 @@ constructor(
|
|||||||
KeyguardIndicationAreaBinder.bind(
|
KeyguardIndicationAreaBinder.bind(
|
||||||
notificationShadeWindowView,
|
notificationShadeWindowView,
|
||||||
keyguardIndicationAreaViewModel,
|
keyguardIndicationAreaViewModel,
|
||||||
indicationController
|
keyguardRootViewModel,
|
||||||
|
indicationController,
|
||||||
|
featureFlags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindKeyguardRootView() {
|
||||||
|
rootViewHandle?.dispose()
|
||||||
|
rootViewHandle = KeyguardRootViewBinder.bind(
|
||||||
|
keyguardRootView,
|
||||||
|
keyguardRootViewModel,
|
||||||
|
featureFlags,
|
||||||
|
occludingAppDeviceEntryMessageViewModel,
|
||||||
|
chipbarCoordinator,
|
||||||
|
keyguardStateController,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +174,84 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindKeyguardRootView() {
|
private fun bindAmbientIndicationArea() {
|
||||||
KeyguardRootViewBinder.bind(
|
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
keyguardRootView,
|
ambientIndicationAreaHandle?.destroy()
|
||||||
featureFlags,
|
ambientIndicationAreaHandle =
|
||||||
occludingAppDeviceEntryMessageViewModel,
|
KeyguardAmbientIndicationAreaViewBinder.bind(
|
||||||
chipbarCoordinator,
|
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.BiometricUnlockSource
|
||||||
import com.android.systemui.keyguard.shared.model.DozeStateModel
|
import com.android.systemui.keyguard.shared.model.DozeStateModel
|
||||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
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.ScreenModel
|
||||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||||
@@ -76,6 +77,8 @@ interface KeyguardRepository {
|
|||||||
*/
|
*/
|
||||||
val bottomAreaAlpha: StateFlow<Float>
|
val bottomAreaAlpha: StateFlow<Float>
|
||||||
|
|
||||||
|
val keyguardAlpha: StateFlow<Float>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable of the relative offset of the lock-screen clock from its natural position on the
|
* Observable of the relative offset of the lock-screen clock from its natural position on the
|
||||||
* screen.
|
* screen.
|
||||||
@@ -170,6 +173,9 @@ interface KeyguardRepository {
|
|||||||
/** Whether quick settings or quick-quick settings is visible. */
|
/** Whether quick settings or quick-quick settings is visible. */
|
||||||
val isQuickSettingsVisible: Flow<Boolean>
|
val isQuickSettingsVisible: Flow<Boolean>
|
||||||
|
|
||||||
|
/** Represents the current state of the KeyguardRootView visibility */
|
||||||
|
val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
|
||||||
|
|
||||||
/** Receive an event for doze time tick */
|
/** Receive an event for doze time tick */
|
||||||
val dozeTimeTick: Flow<Unit>
|
val dozeTimeTick: Flow<Unit>
|
||||||
|
|
||||||
@@ -194,8 +200,14 @@ interface KeyguardRepository {
|
|||||||
fun setAnimateDozingTransitions(animate: Boolean)
|
fun setAnimateDozingTransitions(animate: Boolean)
|
||||||
|
|
||||||
/** Sets the current amount of alpha that should be used for rendering the bottom area. */
|
/** 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)
|
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.
|
* 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)
|
private val _bottomAreaAlpha = MutableStateFlow(1f)
|
||||||
override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
|
override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
|
||||||
|
|
||||||
|
private val _keyguardAlpha = MutableStateFlow(1f)
|
||||||
|
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
|
||||||
|
|
||||||
private val _clockPosition = MutableStateFlow(Position(0, 0))
|
private val _clockPosition = MutableStateFlow(Position(0, 0))
|
||||||
override val clockPosition = _clockPosition.asStateFlow()
|
override val clockPosition = _clockPosition.asStateFlow()
|
||||||
|
|
||||||
@@ -686,6 +701,17 @@ constructor(
|
|||||||
private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
|
private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
|
||||||
override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
|
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) {
|
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||||
_animateBottomAreaDozingTransitions.value = animate
|
_animateBottomAreaDozingTransitions.value = animate
|
||||||
}
|
}
|
||||||
@@ -694,6 +720,23 @@ constructor(
|
|||||||
_bottomAreaAlpha.value = alpha
|
_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) {
|
override fun setClockPosition(x: Int, y: Int) {
|
||||||
_clockPosition.value = Position(x, y)
|
_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.bouncer.data.repository.KeyguardBouncerRepository
|
||||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
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.common.ui.data.repository.ConfigurationRepository
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.flags.FeatureFlags
|
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
|
||||||
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
|
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.DozeTransitionModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
|
||||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
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.statusbar.CommandQueue
|
||||||
import com.android.systemui.util.kotlin.sample
|
import com.android.systemui.util.kotlin.sample
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -63,6 +67,18 @@ constructor(
|
|||||||
bouncerRepository: KeyguardBouncerRepository,
|
bouncerRepository: KeyguardBouncerRepository,
|
||||||
configurationRepository: ConfigurationRepository,
|
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
|
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
|
||||||
* all.
|
* all.
|
||||||
@@ -142,8 +158,6 @@ constructor(
|
|||||||
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
|
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
|
||||||
/** Observable for the [StatusBarState] */
|
/** Observable for the [StatusBarState] */
|
||||||
val statusBarState: Flow<StatusBarState> = repository.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,
|
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
|
||||||
* side, under display) is used to unlock the device.
|
* side, under display) is used to unlock the device.
|
||||||
@@ -182,6 +196,18 @@ constructor(
|
|||||||
/** Notifies when a new configuration is set */
|
/** Notifies when a new configuration is set */
|
||||||
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
|
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> {
|
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
|
||||||
return dozeTransitionModel.filter { states.contains(it.to) }
|
return dozeTransitionModel.filter { states.contains(it.to) }
|
||||||
}
|
}
|
||||||
@@ -211,6 +237,22 @@ constructor(
|
|||||||
repository.setQuickSettingsVisible(isVisible)
|
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 {
|
companion object {
|
||||||
private const val TAG = "KeyguardInteractor"
|
private const val TAG = "KeyguardInteractor"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,10 +101,9 @@ constructor(
|
|||||||
quickAffordanceAlwaysVisible(position),
|
quickAffordanceAlwaysVisible(position),
|
||||||
keyguardInteractor.isDozing,
|
keyguardInteractor.isDozing,
|
||||||
keyguardInteractor.isKeyguardShowing,
|
keyguardInteractor.isKeyguardShowing,
|
||||||
keyguardInteractor.isQuickSettingsVisible,
|
|
||||||
biometricSettingsRepository.isCurrentUserInLockdown,
|
biometricSettingsRepository.isCurrentUserInLockdown,
|
||||||
) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
|
) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
|
||||||
if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible && !isUserInLockdown) {
|
if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
|
||||||
affordance
|
affordance
|
||||||
} else {
|
} else {
|
||||||
KeyguardQuickAffordanceModel.Hidden
|
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.
|
* view-model to be reused for multiple view/view-binder bindings.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
object KeyguardBottomAreaViewBinder {
|
object KeyguardBottomAreaViewBinder {
|
||||||
|
|
||||||
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
|
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
|
* Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
|
||||||
* it is bound.
|
* it is bound.
|
||||||
*/
|
*/
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
interface Binding {
|
interface Binding {
|
||||||
/**
|
/**
|
||||||
* Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
|
* 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. */
|
/** 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")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun bind(
|
fun bind(
|
||||||
@@ -132,6 +136,8 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
val disposableHandle =
|
val disposableHandle =
|
||||||
view.repeatWhenAttached {
|
view.repeatWhenAttached {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
launch {
|
launch {
|
||||||
viewModel.startButton.collect { buttonModel ->
|
viewModel.startButton.collect { buttonModel ->
|
||||||
updateButton(
|
updateButton(
|
||||||
@@ -144,6 +150,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
launch {
|
launch {
|
||||||
viewModel.endButton.collect { buttonModel ->
|
viewModel.endButton.collect { buttonModel ->
|
||||||
updateButton(
|
updateButton(
|
||||||
@@ -180,6 +187,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
launch {
|
launch {
|
||||||
updateButtonAlpha(
|
updateButtonAlpha(
|
||||||
view = startButton,
|
view = startButton,
|
||||||
@@ -188,6 +196,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
launch {
|
launch {
|
||||||
updateButtonAlpha(
|
updateButtonAlpha(
|
||||||
view = endButton,
|
view = endButton,
|
||||||
@@ -213,6 +222,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
launch {
|
launch {
|
||||||
configurationBasedDimensions.collect { dimensions ->
|
configurationBasedDimensions.collect { dimensions ->
|
||||||
startButton.updateLayoutParams<ViewGroup.LayoutParams> {
|
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")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun updateButton(
|
private fun updateButton(
|
||||||
view: ImageView,
|
view: ImageView,
|
||||||
@@ -394,6 +406,8 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
view.isSelected = viewModel.isSelected
|
view.isSelected = viewModel.isSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
private suspend fun updateButtonAlpha(
|
private suspend fun updateButtonAlpha(
|
||||||
view: View,
|
view: View,
|
||||||
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
|
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
|
||||||
@@ -405,6 +419,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
.collect { view.alpha = it }
|
.collect { view.alpha = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
private fun View.animateVisibility(visible: Boolean) {
|
private fun View.animateVisibility(visible: Boolean) {
|
||||||
animate()
|
animate()
|
||||||
.withStartAction {
|
.withStartAction {
|
||||||
@@ -422,6 +437,8 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
|
//If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
|
||||||
private class OnLongClickListener(
|
private class OnLongClickListener(
|
||||||
private val falsingManager: FalsingManager?,
|
private val falsingManager: FalsingManager?,
|
||||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||||
@@ -455,9 +472,10 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
|
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 class OnClickListener(
|
||||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||||
private val falsingManager: FalsingManager,
|
private val falsingManager: FalsingManager,
|
||||||
@@ -479,6 +497,7 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
||||||
return ConfigurationBasedDimensions(
|
return ConfigurationBasedDimensions(
|
||||||
defaultBurnInPreventionYOffsetPx =
|
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. */
|
/** Opens the wallpaper picker screen after the device is unlocked by the user. */
|
||||||
private fun navigateToLockScreenSettings(
|
private fun navigateToLockScreenSettings(
|
||||||
activityStarter: ActivityStarter,
|
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(
|
private data class ConfigurationBasedDimensions(
|
||||||
val defaultBurnInPreventionYOffsetPx: Int,
|
val defaultBurnInPreventionYOffsetPx: Int,
|
||||||
val buttonSizePx: Size,
|
val buttonSizePx: Size,
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import android.widget.TextView
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.systemui.R
|
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.KeyguardIndicationAreaViewModel
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
|
||||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||||
import kotlinx.coroutines.DisposableHandle
|
import kotlinx.coroutines.DisposableHandle
|
||||||
@@ -49,7 +52,9 @@ object KeyguardIndicationAreaBinder {
|
|||||||
fun bind(
|
fun bind(
|
||||||
view: ViewGroup,
|
view: ViewGroup,
|
||||||
viewModel: KeyguardIndicationAreaViewModel,
|
viewModel: KeyguardIndicationAreaViewModel,
|
||||||
|
keyguardRootViewModel: KeyguardRootViewModel,
|
||||||
indicationController: KeyguardIndicationController,
|
indicationController: KeyguardIndicationController,
|
||||||
|
featureFlags: FeatureFlags,
|
||||||
): DisposableHandle {
|
): DisposableHandle {
|
||||||
val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
|
val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
|
||||||
indicationController.setIndicationArea(indicationArea)
|
indicationController.setIndicationArea(indicationArea)
|
||||||
@@ -66,6 +71,18 @@ object KeyguardIndicationAreaBinder {
|
|||||||
view.repeatWhenAttached {
|
view.repeatWhenAttached {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
launch {
|
launch {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} else {
|
||||||
viewModel.alpha.collect { alpha ->
|
viewModel.alpha.collect { alpha ->
|
||||||
view.importantForAccessibility =
|
view.importantForAccessibility =
|
||||||
if (alpha == 0f) {
|
if (alpha == 0f) {
|
||||||
@@ -77,6 +94,7 @@ object KeyguardIndicationAreaBinder {
|
|||||||
indicationArea.alpha = alpha
|
indicationArea.alpha = alpha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
viewModel.indicationAreaTranslationX.collect { translationX ->
|
viewModel.indicationAreaTranslationX.collect { translationX ->
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.keyguard.ui.binder
|
||||||
|
|
||||||
import android.annotation.DrawableRes
|
import android.annotation.DrawableRes
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.android.app.animation.Interpolators
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.common.shared.model.Text
|
import com.android.systemui.common.shared.model.Text
|
||||||
import com.android.systemui.common.shared.model.TintedIcon
|
import com.android.systemui.common.shared.model.TintedIcon
|
||||||
import com.android.systemui.flags.FeatureFlags
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
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.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
|
||||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
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.ViewPriority
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||||
|
import kotlinx.coroutines.DisposableHandle
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -40,16 +46,19 @@ object KeyguardRootViewBinder {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun bind(
|
fun bind(
|
||||||
view: ViewGroup,
|
view: ViewGroup,
|
||||||
|
viewModel: KeyguardRootViewModel,
|
||||||
featureFlags: FeatureFlags,
|
featureFlags: FeatureFlags,
|
||||||
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
|
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
|
||||||
chipbarCoordinator: ChipbarCoordinator,
|
chipbarCoordinator: ChipbarCoordinator,
|
||||||
) {
|
keyguardStateController: KeyguardStateController,
|
||||||
if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
|
): DisposableHandle {
|
||||||
|
val disposableHandle =
|
||||||
view.repeatWhenAttached {
|
view.repeatWhenAttached {
|
||||||
|
if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
launch {
|
launch {
|
||||||
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
|
occludingAppDeviceEntryMessageViewModel.message.collect {
|
||||||
->
|
biometricMessage ->
|
||||||
if (biometricMessage?.message != null) {
|
if (biometricMessage?.message != null) {
|
||||||
chipbarCoordinator.displayView(
|
chipbarCoordinator.displayView(
|
||||||
createChipbarInfo(
|
createChipbarInfo(
|
||||||
@@ -64,7 +73,47 @@ object KeyguardRootViewBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
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.KeyguardPreviewClockViewBinder
|
||||||
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
|
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.KeyguardBottomAreaViewModel
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
|
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.monet.ColorScheme
|
||||||
import com.android.systemui.plugins.ClockController
|
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.ClockRegistry
|
||||||
import com.android.systemui.shared.clocks.DefaultClockController
|
import com.android.systemui.shared.clocks.DefaultClockController
|
||||||
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
|
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
|
||||||
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
|
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.lockscreen.LockscreenSmartspaceController
|
||||||
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
|
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
@@ -71,6 +81,7 @@ constructor(
|
|||||||
private val clockViewModel: KeyguardPreviewClockViewModel,
|
private val clockViewModel: KeyguardPreviewClockViewModel,
|
||||||
private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
|
private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
|
||||||
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
|
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
|
||||||
|
private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
|
||||||
displayManager: DisplayManager,
|
displayManager: DisplayManager,
|
||||||
private val windowManager: WindowManager,
|
private val windowManager: WindowManager,
|
||||||
private val clockController: ClockEventController,
|
private val clockController: ClockEventController,
|
||||||
@@ -78,6 +89,12 @@ constructor(
|
|||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
|
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
|
||||||
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
|
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,
|
@Assisted bundle: Bundle,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -106,7 +123,18 @@ constructor(
|
|||||||
private val disposables = mutableSetOf<DisposableHandle>()
|
private val disposables = mutableSetOf<DisposableHandle>()
|
||||||
private var isDestroyed = false
|
private var isDestroyed = false
|
||||||
|
|
||||||
|
private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
keyguardRootViewModel.enablePreviewMode(
|
||||||
|
initiallySelectedSlotId =
|
||||||
|
bundle.getString(
|
||||||
|
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
|
||||||
|
),
|
||||||
|
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
bottomAreaViewModel.enablePreviewMode(
|
bottomAreaViewModel.enablePreviewMode(
|
||||||
initiallySelectedSlotId =
|
initiallySelectedSlotId =
|
||||||
bundle.getString(
|
bundle.getString(
|
||||||
@@ -114,6 +142,7 @@ constructor(
|
|||||||
),
|
),
|
||||||
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
runBlocking(mainDispatcher) {
|
runBlocking(mainDispatcher) {
|
||||||
host =
|
host =
|
||||||
SurfaceControlViewHost(
|
SurfaceControlViewHost(
|
||||||
@@ -130,7 +159,20 @@ constructor(
|
|||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
val rootView = FrameLayout(context)
|
val rootView = FrameLayout(context)
|
||||||
|
|
||||||
|
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)
|
setUpBottomArea(rootView)
|
||||||
|
}
|
||||||
|
|
||||||
setUpSmartspace(rootView)
|
setUpSmartspace(rootView)
|
||||||
smartSpaceView?.let {
|
smartSpaceView?.let {
|
||||||
@@ -182,13 +224,20 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onSlotSelected(slotId: String) {
|
fun onSlotSelected(slotId: String) {
|
||||||
|
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||||
|
} else {
|
||||||
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
|
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
isDestroyed = true
|
isDestroyed = true
|
||||||
lockscreenSmartspaceController.disconnect()
|
lockscreenSmartspaceController.disconnect()
|
||||||
disposables.forEach { it.dispose() }
|
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
|
smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
private fun setUpBottomArea(parentView: ViewGroup) {
|
private fun setUpBottomArea(parentView: ViewGroup) {
|
||||||
val bottomAreaView =
|
val bottomAreaView =
|
||||||
LayoutInflater.from(context)
|
LayoutInflater.from(context)
|
||||||
@@ -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) {
|
private fun setUpUdfps(parentView: ViewGroup) {
|
||||||
val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
|
val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,13 @@ package com.android.systemui.keyguard.ui.view
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
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.keyguard.LockIconView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.view.LaunchableImageView
|
||||||
|
|
||||||
/** Provides a container for all keyguard ui content. */
|
/** Provides a container for all keyguard ui content. */
|
||||||
class KeyguardRootView(
|
class KeyguardRootView(
|
||||||
@@ -36,6 +41,10 @@ class KeyguardRootView(
|
|||||||
init {
|
init {
|
||||||
addIndicationTextArea()
|
addIndicationTextArea()
|
||||||
addLockIconView()
|
addLockIconView()
|
||||||
|
addAmbientIndicationArea()
|
||||||
|
addLeftShortcut()
|
||||||
|
addRightShortcut()
|
||||||
|
addSettingsPopupMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addIndicationTextArea() {
|
private fun addIndicationTextArea() {
|
||||||
@@ -47,4 +56,65 @@ class KeyguardRootView(
|
|||||||
val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
|
val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
|
||||||
addView(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.View
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
|
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
|
||||||
import androidx.constraintlayout.widget.ConstraintSet.END
|
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.PARENT_ID
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
|
||||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||||
|
import androidx.core.view.setPadding
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor
|
import com.android.keyguard.KeyguardUpdateMonitor
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.view.LaunchableLinearLayout
|
||||||
import com.android.systemui.biometrics.AuthController
|
import com.android.systemui.biometrics.AuthController
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
import com.android.systemui.keyguard.ui.view.KeyguardRootView
|
||||||
@@ -136,14 +146,138 @@ 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 {
|
private fun Int.dp(): Int {
|
||||||
return context.resources.getDimensionPixelSize(this)
|
return context.resources.getDimensionPixelSize(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
|
private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
|
||||||
val cs = ConstraintSet()
|
ConstraintSet().also {
|
||||||
cs.clone(this)
|
it.clone(this)
|
||||||
return cs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ constructor(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun layoutViews() {
|
fun layoutViews(rootView: KeyguardRootView = keyguardRootView) {
|
||||||
layout?.layoutViews(keyguardRootView)
|
layout?.layoutViews(rootView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -85,7 +85,13 @@ interface LockscreenLayout {
|
|||||||
.applyTo(rootView)
|
.applyTo(rootView)
|
||||||
layoutIndicationArea(rootView)
|
layoutIndicationArea(rootView)
|
||||||
layoutLockIcon(rootView)
|
layoutLockIcon(rootView)
|
||||||
|
layoutShortcuts(rootView)
|
||||||
|
layoutAmbientIndicationArea(rootView)
|
||||||
|
layoutSettingsPopupMenu(rootView)
|
||||||
}
|
}
|
||||||
fun layoutIndicationArea(rootView: KeyguardRootView)
|
fun layoutIndicationArea(rootView: KeyguardRootView)
|
||||||
fun layoutLockIcon(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(
|
abstract fun bindDefaultLayout(
|
||||||
defaultLockscreenLayout: DefaultLockscreenLayout
|
defaultLockscreenLayout: DefaultLockscreenLayout
|
||||||
): LockscreenLayout
|
): 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
|
package com.android.systemui.keyguard.ui.viewmodel
|
||||||
|
|
||||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
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.KeyguardBottomAreaInteractor
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -33,6 +35,8 @@ constructor(
|
|||||||
bottomAreaInteractor: KeyguardBottomAreaInteractor,
|
bottomAreaInteractor: KeyguardBottomAreaInteractor,
|
||||||
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
|
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
|
||||||
private val burnInHelperWrapper: BurnInHelperWrapper,
|
private val burnInHelperWrapper: BurnInHelperWrapper,
|
||||||
|
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
|
||||||
|
private val featureFlags: FeatureFlags,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/** Notifies when a new configuration is set */
|
/** Notifies when a new configuration is set */
|
||||||
@@ -43,15 +47,28 @@ constructor(
|
|||||||
|
|
||||||
/** An observable for whether the indication area should be padded. */
|
/** An observable for whether the indication area should be padded. */
|
||||||
val isIndicationAreaPadded: Flow<Boolean> =
|
val isIndicationAreaPadded: Flow<Boolean> =
|
||||||
|
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
|
||||||
|
startButtonModel,
|
||||||
|
endButtonModel ->
|
||||||
|
startButtonModel.isVisible || endButtonModel.isVisible
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
} else {
|
||||||
combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
|
combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
|
||||||
startButtonModel,
|
startButtonModel,
|
||||||
endButtonModel ->
|
endButtonModel ->
|
||||||
startButtonModel.isVisible || endButtonModel.isVisible
|
startButtonModel.isVisible || endButtonModel.isVisible
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
/** An observable for the x-offset by which the indication area should be translated. */
|
/** An observable for the x-offset by which the indication area should be translated. */
|
||||||
val indicationAreaTranslationX: Flow<Float> =
|
val indicationAreaTranslationX: Flow<Float> =
|
||||||
|
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||||
|
} else {
|
||||||
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns an observable for the y-offset by which the indication area should be translated. */
|
/** Returns an observable for the y-offset by which the indication area should be translated. */
|
||||||
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
|
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.TransitionStep;
|
||||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel;
|
import com.android.systemui.keyguard.shared.model.WakefulnessModel;
|
||||||
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
|
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.DreamingToLockscreenTransitionViewModel;
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
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 AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
|
||||||
private final NotificationGutsManager mGutsManager;
|
private final NotificationGutsManager mGutsManager;
|
||||||
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
|
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
|
||||||
|
private final KeyguardRootView mKeyguardRootView;
|
||||||
private final QuickSettingsController mQsController;
|
private final QuickSettingsController mQsController;
|
||||||
private final TouchHandler mTouchHandler = new TouchHandler();
|
private final TouchHandler mTouchHandler = new TouchHandler();
|
||||||
|
|
||||||
@@ -363,6 +365,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
private boolean mTracking;
|
private boolean mTracking;
|
||||||
private boolean mIsTrackingExpansionFromStatusBar;
|
private boolean mIsTrackingExpansionFromStatusBar;
|
||||||
private boolean mHintAnimationRunning;
|
private boolean mHintAnimationRunning;
|
||||||
|
@Deprecated
|
||||||
private KeyguardBottomAreaView mKeyguardBottomArea;
|
private KeyguardBottomAreaView mKeyguardBottomArea;
|
||||||
private boolean mExpanding;
|
private boolean mExpanding;
|
||||||
private boolean mSplitShadeEnabled;
|
private boolean mSplitShadeEnabled;
|
||||||
@@ -739,7 +742,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
KeyguardInteractor keyguardInteractor,
|
KeyguardInteractor keyguardInteractor,
|
||||||
ActivityStarter activityStarter,
|
ActivityStarter activityStarter,
|
||||||
KeyguardViewConfigurator keyguardViewConfigurator,
|
KeyguardViewConfigurator keyguardViewConfigurator,
|
||||||
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
|
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
|
||||||
|
KeyguardRootView keyguardRootView) {
|
||||||
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
|
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onKeyguardFadingAwayChanged() {
|
public void onKeyguardFadingAwayChanged() {
|
||||||
@@ -939,6 +943,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
mAlternateBouncerInteractor = alternateBouncerInteractor;
|
mAlternateBouncerInteractor = alternateBouncerInteractor;
|
||||||
|
mKeyguardRootView = keyguardRootView;
|
||||||
dumpManager.registerDumpable(this);
|
dumpManager.registerDumpable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,7 +1034,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
|
mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
|
||||||
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
|
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
|
||||||
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
|
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
|
||||||
|
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
|
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
|
||||||
|
}
|
||||||
|
|
||||||
initBottomArea();
|
initBottomArea();
|
||||||
|
|
||||||
@@ -1311,6 +1318,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
|
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
|
||||||
keyguardUserSwitcherView);
|
keyguardUserSwitcherView);
|
||||||
|
|
||||||
|
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
// Update keyguard bottom area
|
// Update keyguard bottom area
|
||||||
int index = mView.indexOfChild(mKeyguardBottomArea);
|
int index = mView.indexOfChild(mKeyguardBottomArea);
|
||||||
mView.removeView(mKeyguardBottomArea);
|
mView.removeView(mKeyguardBottomArea);
|
||||||
@@ -1318,7 +1326,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
|
setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
|
||||||
mKeyguardBottomArea.initFrom(oldBottomArea);
|
mKeyguardBottomArea.initFrom(oldBottomArea);
|
||||||
mView.addView(mKeyguardBottomArea, index);
|
mView.addView(mKeyguardBottomArea, index);
|
||||||
|
|
||||||
initBottomArea();
|
initBottomArea();
|
||||||
|
}
|
||||||
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
|
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
|
||||||
mStatusBarStateController.getInterpolatedDozeAmount());
|
mStatusBarStateController.getInterpolatedDozeAmount());
|
||||||
|
|
||||||
@@ -1341,7 +1351,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
false,
|
false,
|
||||||
mBarState);
|
mBarState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
setKeyguardVisibility(mBarState, false);
|
||||||
|
} else {
|
||||||
setKeyguardBottomAreaVisibility(mBarState, false);
|
setKeyguardBottomAreaVisibility(mBarState, false);
|
||||||
|
}
|
||||||
|
|
||||||
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
|
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
|
||||||
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
|
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
|
||||||
@@ -1352,6 +1367,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initBottomArea() {
|
private void initBottomArea() {
|
||||||
|
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
mKeyguardBottomArea.init(
|
mKeyguardBottomArea.init(
|
||||||
mKeyguardBottomAreaViewModel,
|
mKeyguardBottomAreaViewModel,
|
||||||
mFalsingManager,
|
mFalsingManager,
|
||||||
@@ -1362,7 +1378,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mActivityStarter);
|
mActivityStarter);
|
||||||
|
|
||||||
// Rebind (for now), as a new bottom area and indication area may have been created
|
// Rebind (for now), as a new bottom area and indication area may have been created
|
||||||
mKeyguardViewConfigurator.bindIndicationArea(mKeyguardBottomArea);
|
mKeyguardViewConfigurator.bindIndicationArea();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -1398,6 +1415,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
|
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
|
private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
|
||||||
mKeyguardBottomArea = keyguardBottomArea;
|
mKeyguardBottomArea = keyguardBottomArea;
|
||||||
}
|
}
|
||||||
@@ -1521,8 +1539,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mClockPositionAlgorithm.run(mClockPositionResult);
|
mClockPositionAlgorithm.run(mClockPositionResult);
|
||||||
mKeyguardStatusViewController.setLockscreenClockY(
|
mKeyguardStatusViewController.setLockscreenClockY(
|
||||||
mClockPositionAlgorithm.getExpandedPreferredClockY());
|
mClockPositionAlgorithm.getExpandedPreferredClockY());
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
mKeyguardInteractor.setClockPosition(
|
||||||
|
mClockPositionResult.clockX, mClockPositionResult.clockY);
|
||||||
|
} else {
|
||||||
mKeyguardBottomAreaInteractor.setClockPosition(
|
mKeyguardBottomAreaInteractor.setClockPosition(
|
||||||
mClockPositionResult.clockX, mClockPositionResult.clockY);
|
mClockPositionResult.clockX, mClockPositionResult.clockY);
|
||||||
|
}
|
||||||
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
|
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
|
||||||
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
|
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
|
||||||
mKeyguardStatusViewController.updatePosition(
|
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) {
|
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
|
||||||
mKeyguardBottomArea.animate().cancel();
|
mKeyguardBottomArea.animate().cancel();
|
||||||
if (goingToFullShade) {
|
if (goingToFullShade) {
|
||||||
@@ -2177,8 +2209,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
|
mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
|
||||||
Interpolators.ALPHA_OUT).withEndAction(
|
Interpolators.ALPHA_OUT).withEndAction(
|
||||||
mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
|
mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
|
||||||
} else if (statusBarState == KEYGUARD
|
} else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) {
|
||||||
|| statusBarState == StatusBarState.SHADE_LOCKED) {
|
|
||||||
mKeyguardBottomArea.setVisibility(View.VISIBLE);
|
mKeyguardBottomArea.setVisibility(View.VISIBLE);
|
||||||
if (!mIsOcclusionTransitionRunning) {
|
if (!mIsOcclusionTransitionRunning) {
|
||||||
mKeyguardBottomArea.setAlpha(1f);
|
mKeyguardBottomArea.setAlpha(1f);
|
||||||
@@ -2514,7 +2545,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
getExpandedFraction());
|
getExpandedFraction());
|
||||||
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
|
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
|
||||||
alpha *= mBottomAreaShadeAlpha;
|
alpha *= mBottomAreaShadeAlpha;
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
mKeyguardInteractor.setAlpha(alpha);
|
||||||
|
} else {
|
||||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||||
|
}
|
||||||
mLockIconViewController.setAlpha(alpha);
|
mLockIconViewController.setAlpha(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2773,7 +2808,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateDozingVisibilities(boolean animate) {
|
private void updateDozingVisibilities(boolean animate) {
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
mKeyguardInteractor.setAnimateDozingTransitions(animate);
|
||||||
|
} else {
|
||||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||||
|
}
|
||||||
if (!mDozing && animate) {
|
if (!mDozing && animate) {
|
||||||
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
|
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
|
||||||
}
|
}
|
||||||
@@ -2977,7 +3016,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mDozing = dozing;
|
mDozing = dozing;
|
||||||
// TODO (b/) make listeners for this
|
// TODO (b/) make listeners for this
|
||||||
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
|
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
mKeyguardInteractor.setAnimateDozingTransitions(animate);
|
||||||
|
} else {
|
||||||
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
|
||||||
|
}
|
||||||
mKeyguardStatusBarViewController.setDozing(mDozing);
|
mKeyguardStatusBarViewController.setDozing(mDozing);
|
||||||
mQsController.setDozing(mDozing);
|
mQsController.setDozing(mDozing);
|
||||||
|
|
||||||
@@ -3898,8 +3941,23 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
animator.start();
|
animator.start();
|
||||||
setAnimator(animator);
|
setAnimator(animator);
|
||||||
|
|
||||||
|
|
||||||
|
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(() -> mKeyguardRootViewAnimator
|
||||||
|
.translationY(0)
|
||||||
|
.setDuration(450)
|
||||||
|
.setInterpolator(mBounceInterpolator)
|
||||||
|
.start())
|
||||||
|
.start();
|
||||||
|
} else {
|
||||||
final List<ViewPropertyAnimator> indicationAnimators =
|
final List<ViewPropertyAnimator> indicationAnimators =
|
||||||
mKeyguardBottomArea.getIndicationAreaAnimators();
|
mKeyguardBottomArea.getIndicationAreaAnimators();
|
||||||
|
|
||||||
for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
|
for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
|
||||||
indicationAreaAnimator
|
indicationAreaAnimator
|
||||||
.translationY(-mHintDistance)
|
.translationY(-mHintDistance)
|
||||||
@@ -3914,6 +3972,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void setAnimator(ValueAnimator animator) {
|
private void setAnimator(ValueAnimator animator) {
|
||||||
mHeightAnimator = animator;
|
mHeightAnimator = animator;
|
||||||
if (animator == null && mPanelUpdateWhenAnimatorEnds) {
|
if (animator == null && mPanelUpdateWhenAnimatorEnds) {
|
||||||
@@ -4352,7 +4412,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
goingToFullShade,
|
goingToFullShade,
|
||||||
mBarState);
|
mBarState);
|
||||||
|
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
setKeyguardVisibility(statusBarState, goingToFullShade);
|
||||||
|
} else {
|
||||||
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
|
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: maybe add a listener for barstate
|
// TODO: maybe add a listener for barstate
|
||||||
mBarState = statusBarState;
|
mBarState = statusBarState;
|
||||||
@@ -4589,7 +4653,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
|
|||||||
mKeyguardStatusViewController.setAlpha(alpha);
|
mKeyguardStatusViewController.setAlpha(alpha);
|
||||||
stackScroller.setAlpha(alpha);
|
stackScroller.setAlpha(alpha);
|
||||||
|
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
|
||||||
|
mKeyguardInteractor.setAlpha(alpha);
|
||||||
|
} else {
|
||||||
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
mKeyguardBottomAreaInteractor.setAlpha(alpha);
|
||||||
|
}
|
||||||
mLockIconViewController.setAlpha(alpha);
|
mLockIconViewController.setAlpha(alpha);
|
||||||
|
|
||||||
if (mKeyguardQsUserSwitchController != null) {
|
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
|
* 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.
|
* indication area between them, though the indication area is primarily controlled elsewhere.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
class KeyguardBottomAreaView
|
class KeyguardBottomAreaView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(
|
constructor(
|
||||||
@@ -53,6 +54,7 @@ constructor(
|
|||||||
defStyleRes,
|
defStyleRes,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
interface MessageDisplayer {
|
interface MessageDisplayer {
|
||||||
fun display(@StringRes stringResourceId: Int)
|
fun display(@StringRes stringResourceId: Int)
|
||||||
}
|
}
|
||||||
@@ -62,6 +64,7 @@ constructor(
|
|||||||
private var lockIconViewController: LockIconViewController? = null
|
private var lockIconViewController: LockIconViewController? = null
|
||||||
|
|
||||||
/** Initializes the view. */
|
/** Initializes the view. */
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
fun init(
|
fun init(
|
||||||
viewModel: KeyguardBottomAreaViewModel,
|
viewModel: KeyguardBottomAreaViewModel,
|
||||||
falsingManager: FalsingManager? = null,
|
falsingManager: FalsingManager? = null,
|
||||||
@@ -88,6 +91,7 @@ constructor(
|
|||||||
* Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
|
* Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
|
||||||
* [KeyguardBottomAreaView]
|
* [KeyguardBottomAreaView]
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
|
fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
|
||||||
// if it exists, continue to use the original ambient indication container
|
// if it exists, continue to use the original ambient indication container
|
||||||
// instead of the newly inflated one
|
// instead of the newly inflated one
|
||||||
@@ -122,9 +126,11 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a list of animators to use to animate the indication areas. */
|
/** Returns a list of animators to use to animate the indication areas. */
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
val indicationAreaAnimators: List<ViewPropertyAnimator>
|
val indicationAreaAnimators: List<ViewPropertyAnimator>
|
||||||
get() = checkNotNull(binding).getIndicationAreaAnimators()
|
get() = checkNotNull(binding).getIndicationAreaAnimators()
|
||||||
|
|
||||||
|
|
||||||
override fun hasOverlappingRendering(): Boolean {
|
override fun hasOverlappingRendering(): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,24 +292,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
|||||||
assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
|
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
|
@Test
|
||||||
fun quickAffordance_hiddenWhenUserIsInLockdownMode() =
|
fun quickAffordance_hiddenWhenUserIsInLockdownMode() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.biometrics.AuthController
|
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.keyguard.ui.view.KeyguardRootView
|
||||||
import com.android.systemui.monet.utils.ArgbSubject.assertThat
|
import com.android.systemui.monet.utils.ArgbSubject.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -42,9 +44,10 @@ class DefaultLockscreenLayoutTest : SysuiTestCase() {
|
|||||||
private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
|
private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
|
||||||
private lateinit var rootView: KeyguardRootView
|
private lateinit var rootView: KeyguardRootView
|
||||||
@Mock private lateinit var authController: AuthController
|
@Mock private lateinit var authController: AuthController
|
||||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
|
||||||
|
|
||||||
|
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.coroutines.collectLastValue
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
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.data.repository.FakeKeyguardRepository
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
|
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
|
||||||
@@ -45,6 +46,8 @@ import org.mockito.MockitoAnnotations
|
|||||||
class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
|
class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
|
@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 underTest: KeyguardIndicationAreaViewModel
|
||||||
private lateinit var repository: FakeKeyguardRepository
|
private lateinit var repository: FakeKeyguardRepository
|
||||||
@@ -83,6 +86,8 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
|
|||||||
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
|
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
|
||||||
keyguardBottomAreaViewModel = bottomAreaViewModel,
|
keyguardBottomAreaViewModel = bottomAreaViewModel,
|
||||||
burnInHelperWrapper = burnInHelperWrapper,
|
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.KeyguardInteractor;
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
|
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
|
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.DreamingToLockscreenTransitionViewModel;
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||||
@@ -314,6 +315,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
|
|||||||
@Mock private ShadeInteractor mShadeInteractor;
|
@Mock private ShadeInteractor mShadeInteractor;
|
||||||
@Mock private JavaAdapter mJavaAdapter;
|
@Mock private JavaAdapter mJavaAdapter;
|
||||||
@Mock private CastController mCastController;
|
@Mock private CastController mCastController;
|
||||||
|
@Mock private KeyguardRootView mKeyguardRootView;
|
||||||
|
|
||||||
protected final int mMaxUdfpsBurnInOffsetY = 5;
|
protected final int mMaxUdfpsBurnInOffsetY = 5;
|
||||||
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
|
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
|
||||||
@@ -618,7 +620,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
|
|||||||
mKeyguardInteractor,
|
mKeyguardInteractor,
|
||||||
mActivityStarter,
|
mActivityStarter,
|
||||||
mKeyguardViewConfigurator,
|
mKeyguardViewConfigurator,
|
||||||
mKeyguardFaceAuthInteractor);
|
mKeyguardFaceAuthInteractor,
|
||||||
|
mKeyguardRootView);
|
||||||
mNotificationPanelViewController.initDependencies(
|
mNotificationPanelViewController.initDependencies(
|
||||||
mCentralSurfaces,
|
mCentralSurfaces,
|
||||||
null,
|
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.BiometricUnlockModel
|
||||||
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
|
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
|
||||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
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.ScreenModel
|
||||||
import com.android.systemui.keyguard.shared.model.ScreenState
|
import com.android.systemui.keyguard.shared.model.ScreenState
|
||||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||||
@@ -115,6 +116,20 @@ class FakeKeyguardRepository : KeyguardRepository {
|
|||||||
private val _isQuickSettingsVisible = MutableStateFlow(false)
|
private val _isQuickSettingsVisible = MutableStateFlow(false)
|
||||||
override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
|
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) {
|
override fun setQuickSettingsVisible(isVisible: Boolean) {
|
||||||
_isQuickSettingsVisible.value = isVisible
|
_isQuickSettingsVisible.value = isVisible
|
||||||
}
|
}
|
||||||
@@ -132,6 +147,8 @@ class FakeKeyguardRepository : KeyguardRepository {
|
|||||||
_animateBottomAreaDozingTransitions.tryEmit(animate)
|
_animateBottomAreaDozingTransitions.tryEmit(animate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated("Deprecated as part of b/278057014")
|
||||||
override fun setBottomAreaAlpha(alpha: Float) {
|
override fun setBottomAreaAlpha(alpha: Float) {
|
||||||
_bottomAreaAlpha.value = alpha
|
_bottomAreaAlpha.value = alpha
|
||||||
}
|
}
|
||||||
@@ -223,4 +240,18 @@ class FakeKeyguardRepository : KeyguardRepository {
|
|||||||
override fun isUdfpsSupported(): Boolean {
|
override fun isUdfpsSupported(): Boolean {
|
||||||
return _isUdfpsSupported.value
|
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