1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.scene.ui.viewmodel 18 19 import android.view.HapticFeedbackConstants 20 import android.view.View 21 import androidx.compose.ui.hapticfeedback.HapticFeedback 22 import androidx.compose.ui.hapticfeedback.HapticFeedbackType 23 import com.android.compose.animation.scene.ObservableTransitionState 24 import com.android.compose.animation.scene.reveal.ContainerRevealHaptics 25 import com.android.systemui.Flags 26 import com.android.systemui.lifecycle.ExclusiveActivatable 27 import com.android.systemui.scene.domain.interactor.SceneInteractor 28 import com.android.systemui.scene.shared.model.Overlays 29 import com.android.systemui.scene.shared.model.Scenes 30 import com.android.systemui.scene.ui.composable.SceneContainer 31 import com.android.systemui.shade.domain.interactor.ShadeInteractor 32 import com.google.android.msdl.data.model.MSDLToken 33 import com.google.android.msdl.domain.MSDLPlayer 34 import dagger.assisted.Assisted 35 import dagger.assisted.AssistedFactory 36 import dagger.assisted.AssistedInject 37 import kotlinx.coroutines.awaitCancellation 38 import kotlinx.coroutines.flow.Flow 39 import kotlinx.coroutines.flow.combine 40 import kotlinx.coroutines.flow.distinctUntilChanged 41 import kotlinx.coroutines.flow.filter 42 43 /** 44 * Models haptics UI state for the scene container. 45 * 46 * This model gets a [View] to play haptics using the [View.performHapticFeedback] API. This should 47 * be the only purpose of this reference. 48 */ 49 class SceneContainerHapticsViewModel 50 @AssistedInject 51 constructor( 52 @Assisted private val view: View, 53 sceneInteractor: SceneInteractor, 54 shadeInteractor: ShadeInteractor, 55 private val msdlPlayer: MSDLPlayer, 56 ) : ExclusiveActivatable() { 57 58 /** Should haptics be played by pulling down the shade */ 59 private val isShadePullHapticsRequired: Flow<Boolean> = 60 combine(shadeInteractor.isUserInteracting, sceneInteractor.transitionState) { 61 interacting, 62 transitionState -> 63 interacting && transitionState.isValidForShadePullHaptics() 64 } 65 .distinctUntilChanged() 66 67 override suspend fun onActivated(): Nothing { 68 playShadePullHaptics() 69 awaitCancellation() 70 } 71 72 /** 73 * Returns a handler for reveal transition haptics in [SceneContainer] for the given 74 * [hapticFeedback] target. 75 */ 76 fun getRevealHaptics(hapticFeedback: HapticFeedback): ContainerRevealHaptics { 77 return object : ContainerRevealHaptics { 78 override fun onRevealThresholdCrossed(revealed: Boolean) { 79 if (revealed) { 80 hapticFeedback.performHapticFeedback( 81 HapticFeedbackType.GestureThresholdActivate 82 ) 83 } 84 } 85 } 86 } 87 88 private suspend fun playShadePullHaptics() { 89 isShadePullHapticsRequired 90 .filter { it } 91 .collect { 92 if (Flags.msdlFeedback()) { 93 msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR) 94 } else { 95 view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) 96 } 97 } 98 } 99 100 private fun ObservableTransitionState.isValidForShadePullHaptics(): Boolean { 101 val validOrigin = 102 isTransitioning(from = Scenes.Gone) || isTransitioning(from = Scenes.Lockscreen) 103 val validDestination = 104 isTransitioning(to = Scenes.Shade) || 105 isTransitioning(to = Scenes.QuickSettings) || 106 isTransitioning(to = Overlays.QuickSettingsShade) || 107 isTransitioning(to = Overlays.NotificationsShade) 108 return validOrigin && validDestination 109 } 110 111 @AssistedFactory 112 interface Factory { 113 fun create(view: View): SceneContainerHapticsViewModel 114 } 115 } 116