Merge "KeyguardBottomAreaView new impl. - UI layer" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-08-02 05:12:43 +00:00
committed by Android (Google) Code Review
9 changed files with 1131 additions and 8 deletions

View File

@@ -59,6 +59,30 @@
</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
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"

View File

@@ -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)
}
}

View File

@@ -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,
)
}

View File

@@ -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()
}
}
}

View File

@@ -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?,
)
}

View File

@@ -128,6 +128,10 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
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.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -697,6 +701,12 @@ public final class NotificationPanelViewController extends PanelViewController {
};
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
public NotificationPanelViewController(NotificationPanelView view,
@@ -767,7 +777,12 @@ public final class NotificationPanelViewController extends PanelViewController {
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
CameraGestureHelper cameraGestureHelper) {
CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
super(view,
falsingManager,
dozeLog,
@@ -897,6 +912,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -950,6 +966,10 @@ public final class NotificationPanelViewController extends PanelViewController {
}
});
mCameraGestureHelper = cameraGestureHelper;
mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
}
@VisibleForTesting
@@ -1274,11 +1294,17 @@ public final class NotificationPanelViewController extends PanelViewController {
}
private void initBottomArea() {
mKeyguardBottomArea.init(
mFalsingManager,
mQuickAccessWalletController,
mControlsComponent,
mQRCodeScannerController);
if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
} else {
// TODO(b/235403546): remove this method call when the new implementation is complete
// and these are not needed.
mKeyguardBottomArea.init(
mFalsingManager,
mQuickAccessWalletController,
mControlsComponent,
mQRCodeScannerController);
}
}
@VisibleForTesting
@@ -1454,6 +1480,8 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
mSetClockPositionUseCaseProvider.get().invoke(
mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
mKeyguardStatusViewController.updatePosition(
@@ -3220,6 +3248,7 @@ public final class NotificationPanelViewController extends PanelViewController {
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3419,6 +3448,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3721,6 +3751,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {

View File

@@ -16,6 +16,7 @@
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.doze.util.BurnInHelperKt.getBurnInOffset;
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.ui.ControlsActivity;
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.FalsingManager;
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) {
this(context, null);
}
@@ -142,13 +148,36 @@ public class KeyguardBottomAreaView extends FrameLayout {
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(
FalsingManager falsingManager,
QuickAccessWalletController controller,
ControlsComponent controlsComponent,
QRCodeScannerController qrCodeScannerController) {
if (mUsesBinder) {
return;
}
Log.i(TAG, "initialized without a binder");
mFalsingManager = falsingManager;
mQuickAccessWalletController = controller;
mQuickAccessWalletController.setupWalletChangeObservers(
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
@@ -174,6 +203,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* another {@link KeyguardBottomAreaView}
*/
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
if (mUsesBinder) {
return;
}
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
@@ -201,6 +234,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (mUsesBinder) {
return;
}
mOverlayContainer = findViewById(R.id.overlay_container);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
@@ -229,6 +266,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mUsesBinder) {
return;
}
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -237,6 +278,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mUsesBinder) {
return;
}
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
@@ -259,6 +304,13 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mUsesBinder) {
if (mBinding != null) {
mBinding.onConfigurationChanged();
}
return;
}
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
@@ -301,6 +353,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateWalletVisibility() {
if (mUsesBinder) {
return;
}
if (mDozing
|| mQuickAccessWalletController == null
|| !mQuickAccessWalletController.isWalletEnabled()
@@ -318,6 +374,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateControlsVisibility() {
if (mUsesBinder) {
return;
}
if (mControlsComponent == null) return;
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
@@ -344,6 +404,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setDarkAmount(float darkAmount) {
if (mUsesBinder) {
return;
}
if (darkAmount == mDarkAmount) {
return;
}
@@ -355,6 +419,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* Returns a list of animators to use to animate the indication areas.
*/
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
if (mUsesBinder) {
return checkNotNull(mBinding).getIndicationAreaAnimators();
}
List<ViewPropertyAnimator> animators =
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
animators.add(mIndicationArea.animate());
@@ -394,6 +462,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setDozing(boolean dozing, boolean animate) {
if (mUsesBinder) {
return;
}
mDozing = dozing;
updateWalletVisibility();
@@ -411,6 +483,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void dozeTimeTick() {
if (mUsesBinder) {
return;
}
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- mBurnInYOffset;
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
@@ -420,6 +496,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setAntiBurnInOffsetX(int burnInXOffset) {
if (mUsesBinder) {
return;
}
if (mBurnInXOffset == burnInXOffset) {
return;
}
@@ -435,6 +515,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* action buttons. Does not set the alpha of the lock icon.
*/
public void setComponentAlphas(float alpha) {
if (mUsesBinder) {
return;
}
setImportantForAccessibility(
alpha == 0f
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -461,6 +545,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateQRCodeButtonVisibility() {
if (mUsesBinder) {
return;
}
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
// Don't enable if quick access wallet is enabled
@@ -481,6 +569,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onQRCodeScannerClicked(View view) {
if (mUsesBinder) {
return;
}
Intent intent = mQRCodeScannerController.getIntent();
if (intent != null) {
try {
@@ -500,6 +592,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateAffordanceColors() {
if (mUsesBinder) {
return;
}
int iconColor = Utils.getColorAttrDefaultColor(
mContext,
com.android.internal.R.attr.textColorPrimary);
@@ -516,6 +612,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onWalletClick(View v) {
if (mUsesBinder) {
return;
}
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
@@ -531,6 +631,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onControlsClick(View v) {
if (mUsesBinder) {
return;
}
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}

View File

@@ -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
}
}

View File

@@ -97,6 +97,10 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
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.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -373,6 +377,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private ViewParent mViewParent;
@Mock
private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private SetClockPositionUseCase mSetClockPositionUseCase;
@Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
@Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -564,7 +573,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
mSystemClock,
mock(CameraGestureHelper.class));
mock(CameraGestureHelper.class),
() -> mKeyguardBottomAreaViewModel,
() -> mSetClockPositionUseCase,
() -> mSetKeyguardBottomAreaAlphaUseCase,
() -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},