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.haptics.msdl.qs 18 19 import android.service.quicksettings.Tile 20 import androidx.compose.runtime.Stable 21 import com.android.systemui.Flags 22 import com.android.systemui.animation.Expandable 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.lifecycle.ExclusiveActivatable 25 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel 26 import com.android.systemui.util.kotlin.pairwise 27 import com.google.android.msdl.data.model.MSDLToken 28 import com.google.android.msdl.domain.MSDLPlayer 29 import dagger.assisted.Assisted 30 import dagger.assisted.AssistedFactory 31 import dagger.assisted.AssistedInject 32 import javax.inject.Inject 33 import kotlinx.coroutines.awaitCancellation 34 import kotlinx.coroutines.flow.Flow 35 import kotlinx.coroutines.flow.MutableStateFlow 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.distinctUntilChanged 38 import kotlinx.coroutines.flow.mapLatest 39 import kotlinx.coroutines.flow.merge 40 import kotlinx.coroutines.flow.transform 41 42 /** A view-model to trigger haptic feedback on Quick Settings tiles */ 43 class TileHapticsViewModel 44 @AssistedInject 45 constructor( 46 private val msdlPlayer: MSDLPlayer, 47 @Assisted private val tileViewModel: TileViewModel, 48 ) : ExclusiveActivatable() { 49 50 private val tileInteractionState = MutableStateFlow(TileInteractionState.IDLE) 51 private val tileAnimationState = MutableStateFlow(TileAnimationState.IDLE) 52 private val canPlayToggleHaptics: Boolean 53 get() = 54 tileAnimationState.value == TileAnimationState.IDLE && 55 tileInteractionState.value == TileInteractionState.CLICKED 56 57 val isIdle: Boolean 58 get() = 59 tileAnimationState.value == TileAnimationState.IDLE && 60 tileInteractionState.value == TileInteractionState.IDLE 61 62 private val toggleHapticsState: Flow<TileHapticsState> = 63 tileViewModel.state 64 .mapLatest { it.state } 65 .pairwise() 66 .transform { (previous, current) -> 67 val toggleState = 68 when { 69 !canPlayToggleHaptics -> TileHapticsState.NO_HAPTICS 70 previous == Tile.STATE_INACTIVE && current == Tile.STATE_ACTIVE -> 71 TileHapticsState.TOGGLE_ON 72 previous == Tile.STATE_ACTIVE && current == Tile.STATE_INACTIVE -> 73 TileHapticsState.TOGGLE_OFF 74 else -> TileHapticsState.NO_HAPTICS 75 } 76 emit(toggleState) 77 } 78 .distinctUntilChanged() 79 80 private val interactionHapticsState: Flow<TileHapticsState> = 81 combine(tileInteractionState, tileAnimationState) { interactionState, animationState -> 82 when { 83 interactionState == TileInteractionState.LONG_CLICKED && 84 animationState == TileAnimationState.ACTIVITY_LAUNCH -> 85 TileHapticsState.LONG_PRESS 86 else -> TileHapticsState.NO_HAPTICS 87 } 88 } 89 .distinctUntilChanged() 90 91 private val hapticsState: Flow<TileHapticsState> = 92 merge(toggleHapticsState, interactionHapticsState) 93 94 override suspend fun onActivated(): Nothing { 95 try { 96 hapticsState.collect { hapticsState -> 97 val tokenToPlay: MSDLToken? = 98 when (hapticsState) { 99 TileHapticsState.TOGGLE_ON -> MSDLToken.SWITCH_ON 100 TileHapticsState.TOGGLE_OFF -> MSDLToken.SWITCH_OFF 101 TileHapticsState.LONG_PRESS -> MSDLToken.LONG_PRESS 102 TileHapticsState.NO_HAPTICS -> null 103 } 104 tokenToPlay?.let { 105 msdlPlayer.playToken(it) 106 resetStates() 107 } 108 } 109 awaitCancellation() 110 } finally { 111 resetStates() 112 } 113 } 114 115 private fun resetStates() { 116 tileInteractionState.value = TileInteractionState.IDLE 117 tileAnimationState.value = TileAnimationState.IDLE 118 } 119 120 fun onDialogDrawingStart() { 121 tileAnimationState.value = TileAnimationState.DIALOG_LAUNCH 122 } 123 124 fun onDialogDrawingEnd() { 125 tileAnimationState.value = TileAnimationState.IDLE 126 } 127 128 fun onActivityLaunchTransitionStart() { 129 tileAnimationState.value = TileAnimationState.ACTIVITY_LAUNCH 130 } 131 132 fun onActivityLaunchTransitionEnd() { 133 tileAnimationState.value = TileAnimationState.IDLE 134 } 135 136 fun setTileInteractionState(actionState: TileInteractionState) { 137 tileInteractionState.value = actionState 138 } 139 140 fun createStateAwareExpandable(baseExpandable: Expandable): Expandable = 141 baseExpandable.withStateAwareness( 142 onDialogDrawingStart = ::onDialogDrawingStart, 143 onDialogDrawingEnd = ::onDialogDrawingEnd, 144 onActivityLaunchTransitionStart = ::onActivityLaunchTransitionStart, 145 onActivityLaunchTransitionEnd = ::onActivityLaunchTransitionEnd, 146 ) 147 148 /** Models the state of haptics to play */ 149 enum class TileHapticsState { 150 TOGGLE_ON, 151 TOGGLE_OFF, 152 LONG_PRESS, 153 NO_HAPTICS, 154 } 155 156 /** Models the interaction that took place on the tile */ 157 enum class TileInteractionState { 158 IDLE, 159 CLICKED, 160 LONG_CLICKED, 161 } 162 163 /** Models the animation state of dialogs and activity launches from a tile */ 164 enum class TileAnimationState { 165 IDLE, 166 DIALOG_LAUNCH, 167 ACTIVITY_LAUNCH, 168 } 169 170 @AssistedFactory 171 interface Factory { 172 fun create(tileViewModel: TileViewModel): TileHapticsViewModel 173 } 174 } 175 176 @SysUISingleton 177 @Stable 178 class TileHapticsViewModelFactoryProvider 179 @Inject 180 constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) { getHapticsViewModelFactorynull181 fun getHapticsViewModelFactory(): TileHapticsViewModel.Factory? = 182 if (Flags.msdlFeedback()) { 183 tileHapticsViewModelFactory 184 } else { 185 null 186 } 187 } 188