Merge "KeyguardBottomAreaView new impl. - UI layer" into tm-qpr-dev am: 68de89974b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19348932 Change-Id: I47e35904f176614ab9adb813880936355062d18d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -59,6 +59,30 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/start_button"
|
||||||
|
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
||||||
|
android:layout_width="@dimen/keyguard_affordance_fixed_width"
|
||||||
|
android:layout_gravity="bottom|start"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:tint="?android:attr/textColorPrimary"
|
||||||
|
android:background="@drawable/keyguard_bottom_affordance_bg"
|
||||||
|
android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
|
||||||
|
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/end_button"
|
||||||
|
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
||||||
|
android:layout_width="@dimen/keyguard_affordance_fixed_width"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:tint="?android:attr/textColorPrimary"
|
||||||
|
android:background="@drawable/keyguard_bottom_affordance_bg"
|
||||||
|
android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
|
||||||
|
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/wallet_button"
|
android:id="@+id/wallet_button"
|
||||||
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.doze.util
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/** Injectable wrapper around `BurnInHelper` functions */
|
||||||
|
class BurnInHelperWrapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
|
||||||
|
return getBurnInOffset(amplitude, xAxis)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.util.Size
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewPropertyAnimator
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.android.settingslib.Utils
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.animation.Interpolators
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
|
||||||
|
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||||
|
import com.android.systemui.plugins.FalsingManager
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a keyguard bottom area view to its view-model.
|
||||||
|
*
|
||||||
|
* To use this properly, users should maintain a one-to-one relationship between the [View] and the
|
||||||
|
* view-binding, binding each view only once. It is okay and expected for the same instance of the
|
||||||
|
* view-model to be reused for multiple view/view-binder bindings.
|
||||||
|
*/
|
||||||
|
object KeyguardBottomAreaViewBinder {
|
||||||
|
|
||||||
|
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Binds the view to the view-model, continuing to update the former based on the latter. */
|
||||||
|
@JvmStatic
|
||||||
|
fun bind(
|
||||||
|
view: ViewGroup,
|
||||||
|
viewModel: KeyguardBottomAreaViewModel,
|
||||||
|
falsingManager: FalsingManager,
|
||||||
|
): Binding {
|
||||||
|
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
|
||||||
|
val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
|
||||||
|
val startButton: ImageView = view.requireViewById(R.id.start_button)
|
||||||
|
val endButton: ImageView = view.requireViewById(R.id.end_button)
|
||||||
|
val overlayContainer: View = view.requireViewById(R.id.overlay_container)
|
||||||
|
val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
|
||||||
|
val indicationTextBottom: TextView =
|
||||||
|
view.requireViewById(R.id.keyguard_indication_text_bottom)
|
||||||
|
|
||||||
|
view.clipChildren = false
|
||||||
|
view.clipToPadding = false
|
||||||
|
|
||||||
|
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
|
||||||
|
|
||||||
|
view.repeatWhenAttached {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
launch {
|
||||||
|
combine(viewModel.startButton, viewModel.animateButtonReveal) {
|
||||||
|
buttonModel,
|
||||||
|
animateReveal ->
|
||||||
|
Pair(buttonModel, animateReveal)
|
||||||
|
}
|
||||||
|
.collect { (buttonModel, animateReveal) ->
|
||||||
|
updateButton(
|
||||||
|
view = startButton,
|
||||||
|
viewModel = buttonModel,
|
||||||
|
animateReveal = animateReveal,
|
||||||
|
falsingManager = falsingManager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
combine(viewModel.endButton, viewModel.animateButtonReveal) {
|
||||||
|
buttonModel,
|
||||||
|
animateReveal ->
|
||||||
|
Pair(buttonModel, animateReveal)
|
||||||
|
}
|
||||||
|
.collect { (buttonModel, animateReveal) ->
|
||||||
|
updateButton(
|
||||||
|
view = endButton,
|
||||||
|
viewModel = buttonModel,
|
||||||
|
animateReveal = animateReveal,
|
||||||
|
falsingManager = falsingManager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.isOverlayContainerVisible.collect { isVisible ->
|
||||||
|
overlayContainer.visibility =
|
||||||
|
if (isVisible) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.alpha.collect { alpha ->
|
||||||
|
view.importantForAccessibility =
|
||||||
|
if (alpha == 0f) {
|
||||||
|
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||||
|
} else {
|
||||||
|
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
ambientIndicationArea?.alpha = alpha
|
||||||
|
indicationArea.alpha = alpha
|
||||||
|
startButton.alpha = alpha
|
||||||
|
endButton.alpha = alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.indicationAreaTranslationX.collect { translationX ->
|
||||||
|
indicationArea.translationX = translationX
|
||||||
|
ambientIndicationArea?.translationX = translationX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
combine(
|
||||||
|
viewModel.isIndicationAreaPadded,
|
||||||
|
configurationBasedDimensions.map { it.indicationAreaPaddingPx },
|
||||||
|
) { isPadded, paddingIfPaddedPx ->
|
||||||
|
if (isPadded) {
|
||||||
|
paddingIfPaddedPx
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collect { paddingPx ->
|
||||||
|
indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
configurationBasedDimensions
|
||||||
|
.map { it.defaultBurnInPreventionYOffsetPx }
|
||||||
|
.flatMapLatest { defaultBurnInOffsetY ->
|
||||||
|
viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
|
||||||
|
}
|
||||||
|
.collect { translationY ->
|
||||||
|
indicationArea.translationY = translationY
|
||||||
|
ambientIndicationArea?.translationY = translationY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
configurationBasedDimensions.collect { dimensions ->
|
||||||
|
indicationText.setTextSize(
|
||||||
|
TypedValue.COMPLEX_UNIT_PX,
|
||||||
|
dimensions.indicationTextSizePx.toFloat(),
|
||||||
|
)
|
||||||
|
indicationTextBottom.setTextSize(
|
||||||
|
TypedValue.COMPLEX_UNIT_PX,
|
||||||
|
dimensions.indicationTextSizePx.toFloat(),
|
||||||
|
)
|
||||||
|
|
||||||
|
startButton.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||||
|
width = dimensions.buttonSizePx.width
|
||||||
|
height = dimensions.buttonSizePx.height
|
||||||
|
}
|
||||||
|
endButton.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||||
|
width = dimensions.buttonSizePx.width
|
||||||
|
height = dimensions.buttonSizePx.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object : Binding {
|
||||||
|
override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
|
||||||
|
return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged() {
|
||||||
|
configurationBasedDimensions.value = loadFromResources(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateButton(
|
||||||
|
view: ImageView,
|
||||||
|
viewModel: KeyguardQuickAffordanceViewModel,
|
||||||
|
animateReveal: Boolean,
|
||||||
|
falsingManager: FalsingManager,
|
||||||
|
) {
|
||||||
|
if (!viewModel.isVisible) {
|
||||||
|
view.isVisible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view.isVisible) {
|
||||||
|
view.isVisible = true
|
||||||
|
if (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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (viewModel.icon) {
|
||||||
|
is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
|
||||||
|
is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.drawable.setTint(
|
||||||
|
Utils.getColorAttrDefaultColor(
|
||||||
|
view.context,
|
||||||
|
com.android.internal.R.attr.textColorPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
view.backgroundTintList =
|
||||||
|
Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
|
||||||
|
|
||||||
|
view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
|
||||||
|
view.setOnClickListener {
|
||||||
|
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.configKey != null) {
|
||||||
|
viewModel.onClicked(
|
||||||
|
KeyguardQuickAffordanceViewModel.OnClickedParameters(
|
||||||
|
configKey = viewModel.configKey,
|
||||||
|
animationController = ActivityLaunchAnimator.Controller.fromView(view),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
|
||||||
|
return ConfigurationBasedDimensions(
|
||||||
|
defaultBurnInPreventionYOffsetPx =
|
||||||
|
view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
|
||||||
|
indicationAreaPaddingPx =
|
||||||
|
view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
|
||||||
|
indicationTextSizePx =
|
||||||
|
view.resources.getDimensionPixelSize(
|
||||||
|
com.android.internal.R.dimen.text_size_small_material,
|
||||||
|
),
|
||||||
|
buttonSizePx =
|
||||||
|
Size(
|
||||||
|
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
|
||||||
|
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ConfigurationBasedDimensions(
|
||||||
|
val defaultBurnInPreventionYOffsetPx: Int,
|
||||||
|
val indicationAreaPaddingPx: Int,
|
||||||
|
val indicationTextSizePx: Int,
|
||||||
|
val buttonSizePx: Size,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.usecase.ObserveAnimateBottomAreaTransitionsUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
/** View-model for the keyguard bottom area view */
|
||||||
|
class KeyguardBottomAreaViewModel
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
|
||||||
|
private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
|
||||||
|
observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
|
||||||
|
observeIsDozingUseCase: ObserveIsDozingUseCase,
|
||||||
|
observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
|
||||||
|
private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
|
||||||
|
observeClockPositionUseCase: ObserveClockPositionUseCase,
|
||||||
|
private val burnInHelperWrapper: BurnInHelperWrapper,
|
||||||
|
) {
|
||||||
|
/** 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)
|
||||||
|
/**
|
||||||
|
* An observable for whether the next time a quick action button becomes visible, it should
|
||||||
|
* animate.
|
||||||
|
*/
|
||||||
|
val animateButtonReveal: Flow<Boolean> =
|
||||||
|
observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
|
||||||
|
/** An observable for whether the overlay container should be visible. */
|
||||||
|
val isOverlayContainerVisible: Flow<Boolean> =
|
||||||
|
observeIsDozingUseCase().map { !it }.distinctUntilChanged()
|
||||||
|
/** An observable for the alpha level for the entire bottom area. */
|
||||||
|
val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
|
||||||
|
/** An observable for whether the indication area should be padded. */
|
||||||
|
val isIndicationAreaPadded: Flow<Boolean> =
|
||||||
|
combine(startButton, endButton) { startButtonModel, endButtonModel ->
|
||||||
|
startButtonModel.isVisible || endButtonModel.isVisible
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
/** An observable for the x-offset by which the indication area should be translated. */
|
||||||
|
val indicationAreaTranslationX: Flow<Float> =
|
||||||
|
observeClockPositionUseCase().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 observeDozeAmountUseCase()
|
||||||
|
.map { dozeAmount ->
|
||||||
|
dozeAmount *
|
||||||
|
(burnInHelperWrapper.burnInOffset(
|
||||||
|
/* amplitude = */ defaultBurnInOffset * 2,
|
||||||
|
/* xAxis= */ false,
|
||||||
|
) - defaultBurnInOffset)
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun button(
|
||||||
|
position: KeyguardQuickAffordancePosition
|
||||||
|
): Flow<KeyguardQuickAffordanceViewModel> {
|
||||||
|
return observeQuickAffordanceUseCase(position)
|
||||||
|
.map { model -> model.toViewModel() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
|
||||||
|
return when (this) {
|
||||||
|
is KeyguardQuickAffordanceModel.Visible ->
|
||||||
|
KeyguardQuickAffordanceViewModel(
|
||||||
|
configKey = configKey,
|
||||||
|
isVisible = true,
|
||||||
|
icon = icon,
|
||||||
|
contentDescriptionResourceId = contentDescriptionResourceId,
|
||||||
|
onClicked = { parameters ->
|
||||||
|
onQuickAffordanceClickedUseCase(
|
||||||
|
configKey = parameters.configKey,
|
||||||
|
animationController = parameters.animationController,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.StringRes
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/** Models the UI state of a keyguard quick affordance button. */
|
||||||
|
data class KeyguardQuickAffordanceViewModel(
|
||||||
|
val configKey: KClass<*>? = null,
|
||||||
|
val isVisible: Boolean = false,
|
||||||
|
val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
|
||||||
|
@StringRes val contentDescriptionResourceId: Int = 0,
|
||||||
|
val onClicked: (OnClickedParameters) -> Unit = {},
|
||||||
|
) {
|
||||||
|
data class OnClickedParameters(
|
||||||
|
val configKey: KClass<*>,
|
||||||
|
val animationController: ActivityLaunchAnimator.Controller?,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -128,6 +128,10 @@ import com.android.systemui.flags.Flags;
|
|||||||
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
|
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
|
||||||
import com.android.systemui.fragments.FragmentService;
|
import com.android.systemui.fragments.FragmentService;
|
||||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||||
import com.android.systemui.media.KeyguardMediaController;
|
import com.android.systemui.media.KeyguardMediaController;
|
||||||
import com.android.systemui.media.MediaDataManager;
|
import com.android.systemui.media.MediaDataManager;
|
||||||
import com.android.systemui.media.MediaHierarchyManager;
|
import com.android.systemui.media.MediaHierarchyManager;
|
||||||
@@ -697,6 +701,12 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private final CameraGestureHelper mCameraGestureHelper;
|
private final CameraGestureHelper mCameraGestureHelper;
|
||||||
|
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
|
||||||
|
private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
|
||||||
|
private final Provider<SetKeyguardBottomAreaAlphaUseCase>
|
||||||
|
mSetKeyguardBottomAreaAlphaUseCaseProvider;
|
||||||
|
private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
|
||||||
|
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NotificationPanelViewController(NotificationPanelView view,
|
public NotificationPanelViewController(NotificationPanelView view,
|
||||||
@@ -767,7 +777,12 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
|
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
|
||||||
ShadeTransitionController shadeTransitionController,
|
ShadeTransitionController shadeTransitionController,
|
||||||
SystemClock systemClock,
|
SystemClock systemClock,
|
||||||
CameraGestureHelper cameraGestureHelper) {
|
CameraGestureHelper cameraGestureHelper,
|
||||||
|
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
|
||||||
|
Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
|
||||||
|
Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
|
||||||
|
Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
|
||||||
|
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
|
||||||
super(view,
|
super(view,
|
||||||
falsingManager,
|
falsingManager,
|
||||||
dozeLog,
|
dozeLog,
|
||||||
@@ -897,6 +912,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
|
|
||||||
mQsFrameTranslateController = qsFrameTranslateController;
|
mQsFrameTranslateController = qsFrameTranslateController;
|
||||||
updateUserSwitcherFlags();
|
updateUserSwitcherFlags();
|
||||||
|
mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
|
||||||
onFinishInflate();
|
onFinishInflate();
|
||||||
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
|
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
|
||||||
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
|
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
|
||||||
@@ -950,6 +966,10 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
mCameraGestureHelper = cameraGestureHelper;
|
mCameraGestureHelper = cameraGestureHelper;
|
||||||
|
mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
|
||||||
|
mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
|
||||||
|
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
|
||||||
|
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -1274,11 +1294,17 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initBottomArea() {
|
private void initBottomArea() {
|
||||||
mKeyguardBottomArea.init(
|
if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
|
||||||
mFalsingManager,
|
mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
|
||||||
mQuickAccessWalletController,
|
} else {
|
||||||
mControlsComponent,
|
// TODO(b/235403546): remove this method call when the new implementation is complete
|
||||||
mQRCodeScannerController);
|
// and these are not needed.
|
||||||
|
mKeyguardBottomArea.init(
|
||||||
|
mFalsingManager,
|
||||||
|
mQuickAccessWalletController,
|
||||||
|
mControlsComponent,
|
||||||
|
mQRCodeScannerController);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -1454,6 +1480,8 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
|
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
|
||||||
mKeyguardStatusViewController.isClockTopAligned());
|
mKeyguardStatusViewController.isClockTopAligned());
|
||||||
mClockPositionAlgorithm.run(mClockPositionResult);
|
mClockPositionAlgorithm.run(mClockPositionResult);
|
||||||
|
mSetClockPositionUseCaseProvider.get().invoke(
|
||||||
|
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(
|
||||||
@@ -3220,6 +3248,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
|
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
|
||||||
alpha *= mBottomAreaShadeAlpha;
|
alpha *= mBottomAreaShadeAlpha;
|
||||||
mKeyguardBottomArea.setComponentAlphas(alpha);
|
mKeyguardBottomArea.setComponentAlphas(alpha);
|
||||||
|
mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
|
||||||
mLockIconViewController.setAlpha(alpha);
|
mLockIconViewController.setAlpha(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3419,6 +3448,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
|
|
||||||
private void updateDozingVisibilities(boolean animate) {
|
private void updateDozingVisibilities(boolean animate) {
|
||||||
mKeyguardBottomArea.setDozing(mDozing, animate);
|
mKeyguardBottomArea.setDozing(mDozing, animate);
|
||||||
|
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
|
||||||
if (!mDozing && animate) {
|
if (!mDozing && animate) {
|
||||||
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
|
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
|
||||||
}
|
}
|
||||||
@@ -3721,6 +3751,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
|||||||
mDozing = dozing;
|
mDozing = dozing;
|
||||||
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
|
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
|
||||||
mKeyguardBottomArea.setDozing(mDozing, animate);
|
mKeyguardBottomArea.setDozing(mDozing, animate);
|
||||||
|
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
|
||||||
mKeyguardStatusBarViewController.setDozing(mDozing);
|
mKeyguardStatusBarViewController.setDozing(mDozing);
|
||||||
|
|
||||||
if (dozing) {
|
if (dozing) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.phone;
|
package com.android.systemui.statusbar.phone;
|
||||||
|
|
||||||
|
import static com.android.internal.util.Preconditions.checkNotNull;
|
||||||
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
|
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
|
||||||
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
|
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
|
||||||
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
|
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
|
||||||
@@ -57,6 +58,8 @@ import com.android.systemui.controls.dagger.ControlsComponent;
|
|||||||
import com.android.systemui.controls.management.ControlsListingController;
|
import com.android.systemui.controls.management.ControlsListingController;
|
||||||
import com.android.systemui.controls.ui.ControlsActivity;
|
import com.android.systemui.controls.ui.ControlsActivity;
|
||||||
import com.android.systemui.controls.ui.ControlsUiController;
|
import com.android.systemui.controls.ui.ControlsUiController;
|
||||||
|
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder;
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
import com.android.systemui.plugins.FalsingManager;
|
import com.android.systemui.plugins.FalsingManager;
|
||||||
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
|
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
|
||||||
@@ -125,6 +128,9 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Nullable private KeyguardBottomAreaViewBinder.Binding mBinding;
|
||||||
|
private boolean mUsesBinder;
|
||||||
|
|
||||||
public KeyguardBottomAreaView(Context context) {
|
public KeyguardBottomAreaView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
@@ -142,13 +148,36 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
|
/**
|
||||||
|
* Initializes the view.
|
||||||
|
*/
|
||||||
|
public void init(
|
||||||
|
final KeyguardBottomAreaViewModel viewModel,
|
||||||
|
final FalsingManager falsingManager) {
|
||||||
|
Log.i(TAG, System.identityHashCode(this) + " initialized with a binder");
|
||||||
|
mUsesBinder = true;
|
||||||
|
mBinding = KeyguardBottomAreaViewBinder.bind(this, viewModel, falsingManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the {@link KeyguardBottomAreaView} with the given dependencies
|
||||||
|
*
|
||||||
|
* @deprecated Use
|
||||||
|
* {@link #init(KeyguardBottomAreaViewModel, FalsingManager)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void init(
|
public void init(
|
||||||
FalsingManager falsingManager,
|
FalsingManager falsingManager,
|
||||||
QuickAccessWalletController controller,
|
QuickAccessWalletController controller,
|
||||||
ControlsComponent controlsComponent,
|
ControlsComponent controlsComponent,
|
||||||
QRCodeScannerController qrCodeScannerController) {
|
QRCodeScannerController qrCodeScannerController) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "initialized without a binder");
|
||||||
mFalsingManager = falsingManager;
|
mFalsingManager = falsingManager;
|
||||||
|
|
||||||
mQuickAccessWalletController = controller;
|
mQuickAccessWalletController = controller;
|
||||||
mQuickAccessWalletController.setupWalletChangeObservers(
|
mQuickAccessWalletController.setupWalletChangeObservers(
|
||||||
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
|
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
|
||||||
@@ -174,6 +203,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
* another {@link KeyguardBottomAreaView}
|
* another {@link KeyguardBottomAreaView}
|
||||||
*/
|
*/
|
||||||
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
|
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
if (mAmbientIndicationArea != null) {
|
if (mAmbientIndicationArea != null) {
|
||||||
@@ -201,6 +234,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mOverlayContainer = findViewById(R.id.overlay_container);
|
mOverlayContainer = findViewById(R.id.overlay_container);
|
||||||
mWalletButton = findViewById(R.id.wallet_button);
|
mWalletButton = findViewById(R.id.wallet_button);
|
||||||
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
|
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
|
||||||
@@ -229,6 +266,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
protected void onAttachedToWindow() {
|
protected void onAttachedToWindow() {
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final IntentFilter filter = new IntentFilter();
|
final IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
|
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
|
||||||
mKeyguardStateController.addCallback(mKeyguardStateCallback);
|
mKeyguardStateController.addCallback(mKeyguardStateCallback);
|
||||||
@@ -237,6 +278,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
|
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
|
||||||
|
|
||||||
if (mQuickAccessWalletController != null) {
|
if (mQuickAccessWalletController != null) {
|
||||||
@@ -259,6 +304,13 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
protected void onConfigurationChanged(Configuration newConfig) {
|
protected void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (mUsesBinder) {
|
||||||
|
if (mBinding != null) {
|
||||||
|
mBinding.onConfigurationChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mIndicationBottomMargin = getResources().getDimensionPixelSize(
|
mIndicationBottomMargin = getResources().getDimensionPixelSize(
|
||||||
R.dimen.keyguard_indication_margin_bottom);
|
R.dimen.keyguard_indication_margin_bottom);
|
||||||
mBurnInYOffset = getResources().getDimensionPixelSize(
|
mBurnInYOffset = getResources().getDimensionPixelSize(
|
||||||
@@ -301,6 +353,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateWalletVisibility() {
|
private void updateWalletVisibility() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mDozing
|
if (mDozing
|
||||||
|| mQuickAccessWalletController == null
|
|| mQuickAccessWalletController == null
|
||||||
|| !mQuickAccessWalletController.isWalletEnabled()
|
|| !mQuickAccessWalletController.isWalletEnabled()
|
||||||
@@ -318,6 +374,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateControlsVisibility() {
|
private void updateControlsVisibility() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mControlsComponent == null) return;
|
if (mControlsComponent == null) return;
|
||||||
|
|
||||||
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
|
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
|
||||||
@@ -344,6 +404,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDarkAmount(float darkAmount) {
|
public void setDarkAmount(float darkAmount) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (darkAmount == mDarkAmount) {
|
if (darkAmount == mDarkAmount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -355,6 +419,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
* Returns a list of animators to use to animate the indication areas.
|
* Returns a list of animators to use to animate the indication areas.
|
||||||
*/
|
*/
|
||||||
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
|
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return checkNotNull(mBinding).getIndicationAreaAnimators();
|
||||||
|
}
|
||||||
|
|
||||||
List<ViewPropertyAnimator> animators =
|
List<ViewPropertyAnimator> animators =
|
||||||
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
|
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
|
||||||
animators.add(mIndicationArea.animate());
|
animators.add(mIndicationArea.animate());
|
||||||
@@ -394,6 +462,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDozing(boolean dozing, boolean animate) {
|
public void setDozing(boolean dozing, boolean animate) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mDozing = dozing;
|
mDozing = dozing;
|
||||||
|
|
||||||
updateWalletVisibility();
|
updateWalletVisibility();
|
||||||
@@ -411,6 +483,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dozeTimeTick() {
|
public void dozeTimeTick() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
|
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
|
||||||
- mBurnInYOffset;
|
- mBurnInYOffset;
|
||||||
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
|
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
|
||||||
@@ -420,6 +496,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAntiBurnInOffsetX(int burnInXOffset) {
|
public void setAntiBurnInOffsetX(int burnInXOffset) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mBurnInXOffset == burnInXOffset) {
|
if (mBurnInXOffset == burnInXOffset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -435,6 +515,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
* action buttons. Does not set the alpha of the lock icon.
|
* action buttons. Does not set the alpha of the lock icon.
|
||||||
*/
|
*/
|
||||||
public void setComponentAlphas(float alpha) {
|
public void setComponentAlphas(float alpha) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setImportantForAccessibility(
|
setImportantForAccessibility(
|
||||||
alpha == 0f
|
alpha == 0f
|
||||||
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||||
@@ -461,6 +545,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateQRCodeButtonVisibility() {
|
private void updateQRCodeButtonVisibility() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mQuickAccessWalletController != null
|
if (mQuickAccessWalletController != null
|
||||||
&& mQuickAccessWalletController.isWalletEnabled()) {
|
&& mQuickAccessWalletController.isWalletEnabled()) {
|
||||||
// Don't enable if quick access wallet is enabled
|
// Don't enable if quick access wallet is enabled
|
||||||
@@ -481,6 +569,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onQRCodeScannerClicked(View view) {
|
private void onQRCodeScannerClicked(View view) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Intent intent = mQRCodeScannerController.getIntent();
|
Intent intent = mQRCodeScannerController.getIntent();
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
try {
|
try {
|
||||||
@@ -500,6 +592,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAffordanceColors() {
|
private void updateAffordanceColors() {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int iconColor = Utils.getColorAttrDefaultColor(
|
int iconColor = Utils.getColorAttrDefaultColor(
|
||||||
mContext,
|
mContext,
|
||||||
com.android.internal.R.attr.textColorPrimary);
|
com.android.internal.R.attr.textColorPrimary);
|
||||||
@@ -516,6 +612,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onWalletClick(View v) {
|
private void onWalletClick(View v) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// More coming here; need to inform the user about how to proceed
|
// More coming here; need to inform the user about how to proceed
|
||||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||||
return;
|
return;
|
||||||
@@ -531,6 +631,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onControlsClick(View v) {
|
private void onControlsClick(View v) {
|
||||||
|
if (mUsesBinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,467 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.content.Intent
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.doze.util.BurnInHelperWrapper
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
|
||||||
|
import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
|
||||||
|
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
|
||||||
|
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
|
||||||
|
|
||||||
|
private lateinit var underTest: KeyguardBottomAreaViewModel
|
||||||
|
|
||||||
|
private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
|
||||||
|
private lateinit var repository: FakeKeyguardRepository
|
||||||
|
private lateinit var isDozingUseCase: ObserveIsDozingUseCase
|
||||||
|
private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
|
||||||
|
private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
|
||||||
|
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||||
|
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||||
|
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
|
||||||
|
.thenReturn(RETURNED_BURN_IN_OFFSET)
|
||||||
|
|
||||||
|
affordanceRepository = FakeKeyguardQuickAffordanceRepository()
|
||||||
|
repository = FakeKeyguardRepository()
|
||||||
|
isDozingUseCase =
|
||||||
|
ObserveIsDozingUseCase(
|
||||||
|
repository = repository,
|
||||||
|
)
|
||||||
|
dozeAmountUseCase =
|
||||||
|
ObserveDozeAmountUseCase(
|
||||||
|
repository = repository,
|
||||||
|
)
|
||||||
|
launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
|
||||||
|
homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
KeyguardBottomAreaViewModel(
|
||||||
|
observeQuickAffordanceUseCase =
|
||||||
|
ObserveKeyguardQuickAffordanceUseCase(
|
||||||
|
repository = affordanceRepository,
|
||||||
|
isDozingUseCase = isDozingUseCase,
|
||||||
|
dozeAmountUseCase = dozeAmountUseCase,
|
||||||
|
),
|
||||||
|
onQuickAffordanceClickedUseCase =
|
||||||
|
OnKeyguardQuickAffordanceClickedUseCase(
|
||||||
|
configs =
|
||||||
|
FakeKeyguardQuickAffordanceConfigs(
|
||||||
|
mapOf(
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_START to
|
||||||
|
listOf(
|
||||||
|
homeControlsQuickAffordanceConfig,
|
||||||
|
),
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_END to
|
||||||
|
listOf(
|
||||||
|
quickAccessWalletAffordanceConfig,
|
||||||
|
qrCodeScannerAffordanceConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
launchAffordanceUseCase = launchQuickAffordanceUseCase,
|
||||||
|
),
|
||||||
|
observeBottomAreaAlphaUseCase =
|
||||||
|
ObserveBottomAreaAlphaUseCase(
|
||||||
|
repository = repository,
|
||||||
|
),
|
||||||
|
observeIsDozingUseCase = isDozingUseCase,
|
||||||
|
observeAnimateBottomAreaTransitionsUseCase =
|
||||||
|
ObserveAnimateBottomAreaTransitionsUseCase(
|
||||||
|
repository = repository,
|
||||||
|
),
|
||||||
|
observeDozeAmountUseCase =
|
||||||
|
ObserveDozeAmountUseCase(
|
||||||
|
repository = repository,
|
||||||
|
),
|
||||||
|
observeClockPositionUseCase =
|
||||||
|
ObserveClockPositionUseCase(
|
||||||
|
repository = repository,
|
||||||
|
),
|
||||||
|
burnInHelperWrapper = burnInHelperWrapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `startButton - present and not dozing - visible model - starts activity on click`() =
|
||||||
|
runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||||
|
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
repository.setDozing(false)
|
||||||
|
val testConfig =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = true,
|
||||||
|
icon = mock(),
|
||||||
|
canShowWhileLocked = false,
|
||||||
|
intent = Intent("action"),
|
||||||
|
)
|
||||||
|
val configKey =
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||||
|
testConfig = testConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertQuickAffordanceViewModel(
|
||||||
|
viewModel = latest,
|
||||||
|
testConfig = testConfig,
|
||||||
|
configKey = configKey,
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `endButton - present and not dozing - visible model - do nothing on click`() =
|
||||||
|
runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||||
|
val job = underTest.endButton.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
repository.setDozing(false)
|
||||||
|
val config =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = true,
|
||||||
|
icon = mock(),
|
||||||
|
canShowWhileLocked = false,
|
||||||
|
intent =
|
||||||
|
null, // This will cause it to tell the system that the click was handled.
|
||||||
|
)
|
||||||
|
val configKey =
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||||
|
testConfig = config,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertQuickAffordanceViewModel(
|
||||||
|
viewModel = latest,
|
||||||
|
testConfig = config,
|
||||||
|
configKey = configKey,
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `startButton - not present and not dozing - model is none`() = runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||||
|
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
repository.setDozing(false)
|
||||||
|
val config =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = false,
|
||||||
|
)
|
||||||
|
val configKey =
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||||
|
testConfig = config,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertQuickAffordanceViewModel(
|
||||||
|
viewModel = latest,
|
||||||
|
testConfig = config,
|
||||||
|
configKey = configKey,
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `startButton - present but dozing - model is none`() = runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||||
|
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
repository.setDozing(true)
|
||||||
|
val config =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = true,
|
||||||
|
icon = mock(),
|
||||||
|
canShowWhileLocked = false,
|
||||||
|
intent = Intent("action"),
|
||||||
|
)
|
||||||
|
val configKey =
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||||
|
testConfig = config,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertQuickAffordanceViewModel(
|
||||||
|
viewModel = latest,
|
||||||
|
testConfig = TestConfig(isVisible = false),
|
||||||
|
configKey = configKey,
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animateButtonReveal() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Boolean>()
|
||||||
|
val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
repository.setAnimateDozingTransitions(true)
|
||||||
|
repository.setAnimateDozingTransitions(false)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(listOf(false, true, false))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isOverlayContainerVisible() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Boolean>()
|
||||||
|
val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
repository.setDozing(true)
|
||||||
|
repository.setDozing(false)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(listOf(true, false, true))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun alpha() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Float>()
|
||||||
|
val job = underTest.alpha.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
repository.setBottomAreaAlpha(0.1f)
|
||||||
|
repository.setBottomAreaAlpha(0.5f)
|
||||||
|
repository.setBottomAreaAlpha(0.2f)
|
||||||
|
repository.setBottomAreaAlpha(0f)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isIndicationAreaPadded() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Boolean>()
|
||||||
|
val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||||
|
testConfig =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = true,
|
||||||
|
icon = mock(),
|
||||||
|
canShowWhileLocked = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||||
|
testConfig =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = true,
|
||||||
|
icon = mock(),
|
||||||
|
canShowWhileLocked = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||||
|
testConfig =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setUpQuickAffordanceModel(
|
||||||
|
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||||
|
testConfig =
|
||||||
|
TestConfig(
|
||||||
|
isVisible = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(values)
|
||||||
|
.isEqualTo(
|
||||||
|
listOf(
|
||||||
|
// Initially, no button is visible so the indication area is not padded.
|
||||||
|
false,
|
||||||
|
// Once we add the first visible button, the indication area becomes padded.
|
||||||
|
// This
|
||||||
|
// continues to be true after we add the second visible button and even after we
|
||||||
|
// make the first button not visible anymore.
|
||||||
|
true,
|
||||||
|
// Once both buttons are not visible, the indication area is, again, not padded.
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun indicationAreaTranslationX() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Float>()
|
||||||
|
val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
repository.setClockPosition(100, 100)
|
||||||
|
repository.setClockPosition(200, 100)
|
||||||
|
repository.setClockPosition(200, 200)
|
||||||
|
repository.setClockPosition(300, 100)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun indicationAreaTranslationY() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Float>()
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
|
||||||
|
.onEach(values::add)
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
val expectedTranslationValues =
|
||||||
|
listOf(
|
||||||
|
-0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
|
||||||
|
// FML
|
||||||
|
setDozeAmountAndCalculateExpectedTranslationY(0.1f),
|
||||||
|
setDozeAmountAndCalculateExpectedTranslationY(0.2f),
|
||||||
|
setDozeAmountAndCalculateExpectedTranslationY(0.5f),
|
||||||
|
setDozeAmountAndCalculateExpectedTranslationY(1f),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(expectedTranslationValues)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
|
||||||
|
repository.setDozeAmount(dozeAmount)
|
||||||
|
return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun setUpQuickAffordanceModel(
|
||||||
|
position: KeyguardQuickAffordancePosition,
|
||||||
|
testConfig: TestConfig,
|
||||||
|
): KClass<*> {
|
||||||
|
val config =
|
||||||
|
when (position) {
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
affordanceRepository.setModel(
|
||||||
|
position = position,
|
||||||
|
model =
|
||||||
|
if (testConfig.isVisible) {
|
||||||
|
if (testConfig.intent != null) {
|
||||||
|
config.onClickedResult =
|
||||||
|
KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
|
||||||
|
intent = testConfig.intent,
|
||||||
|
canShowWhileLocked = testConfig.canShowWhileLocked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KeyguardQuickAffordanceModel.Visible(
|
||||||
|
configKey = config::class,
|
||||||
|
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceModel.Hidden
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return config::class
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertQuickAffordanceViewModel(
|
||||||
|
viewModel: KeyguardQuickAffordanceViewModel?,
|
||||||
|
testConfig: TestConfig,
|
||||||
|
configKey: KClass<*>,
|
||||||
|
) {
|
||||||
|
checkNotNull(viewModel)
|
||||||
|
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
|
||||||
|
if (testConfig.isVisible) {
|
||||||
|
assertThat(viewModel.icon).isEqualTo(testConfig.icon)
|
||||||
|
viewModel.onClicked.invoke(
|
||||||
|
KeyguardQuickAffordanceViewModel.OnClickedParameters(
|
||||||
|
configKey = configKey,
|
||||||
|
animationController = animationController,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
testConfig.intent?.let { intent ->
|
||||||
|
assertThat(launchQuickAffordanceUseCase.invocations)
|
||||||
|
.isEqualTo(
|
||||||
|
listOf(
|
||||||
|
FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
|
||||||
|
intent = intent,
|
||||||
|
canShowWhileLocked = testConfig.canShowWhileLocked,
|
||||||
|
animationController = animationController,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
|
||||||
|
} else {
|
||||||
|
assertThat(viewModel.isVisible).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TestConfig(
|
||||||
|
val isVisible: Boolean,
|
||||||
|
val icon: ContainedDrawable? = null,
|
||||||
|
val canShowWhileLocked: Boolean = false,
|
||||||
|
val intent: Intent? = null,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEFAULT_BURN_IN_OFFSET = 5
|
||||||
|
private const val RETURNED_BURN_IN_OFFSET = 3
|
||||||
|
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,10 @@ import com.android.systemui.flags.FeatureFlags;
|
|||||||
import com.android.systemui.fragments.FragmentHostManager;
|
import com.android.systemui.fragments.FragmentHostManager;
|
||||||
import com.android.systemui.fragments.FragmentService;
|
import com.android.systemui.fragments.FragmentService;
|
||||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
|
||||||
|
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
|
||||||
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
|
||||||
import com.android.systemui.media.KeyguardMediaController;
|
import com.android.systemui.media.KeyguardMediaController;
|
||||||
import com.android.systemui.media.MediaDataManager;
|
import com.android.systemui.media.MediaDataManager;
|
||||||
import com.android.systemui.media.MediaHierarchyManager;
|
import com.android.systemui.media.MediaHierarchyManager;
|
||||||
@@ -373,6 +377,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
|||||||
private ViewParent mViewParent;
|
private ViewParent mViewParent;
|
||||||
@Mock
|
@Mock
|
||||||
private ViewTreeObserver mViewTreeObserver;
|
private ViewTreeObserver mViewTreeObserver;
|
||||||
|
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
|
||||||
|
@Mock private SetClockPositionUseCase mSetClockPositionUseCase;
|
||||||
|
@Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
|
||||||
|
@Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
|
||||||
|
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
|
||||||
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
|
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
|
||||||
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
|
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
|
||||||
private SysuiStatusBarStateController mStatusBarStateController;
|
private SysuiStatusBarStateController mStatusBarStateController;
|
||||||
@@ -564,7 +573,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
|||||||
mUnlockedScreenOffAnimationController,
|
mUnlockedScreenOffAnimationController,
|
||||||
mShadeTransitionController,
|
mShadeTransitionController,
|
||||||
mSystemClock,
|
mSystemClock,
|
||||||
mock(CameraGestureHelper.class));
|
mock(CameraGestureHelper.class),
|
||||||
|
() -> mKeyguardBottomAreaViewModel,
|
||||||
|
() -> mSetClockPositionUseCase,
|
||||||
|
() -> mSetKeyguardBottomAreaAlphaUseCase,
|
||||||
|
() -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
|
||||||
mNotificationPanelViewController.initDependencies(
|
mNotificationPanelViewController.initDependencies(
|
||||||
mCentralSurfaces,
|
mCentralSurfaces,
|
||||||
() -> {},
|
() -> {},
|
||||||
|
|||||||
Reference in New Issue
Block a user