1 /* 2 * Copyright (C) 2023 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 18 package com.android.systemui.keyguard.ui.viewmodel 19 20 import android.os.Handler 21 import android.transition.Transition 22 import android.transition.TransitionManager 23 import androidx.constraintlayout.widget.ConstraintLayout 24 import com.android.systemui.dagger.qualifiers.Main 25 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor 26 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 27 import com.android.systemui.keyguard.shared.model.KeyguardState 28 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition 29 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config 30 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type 31 import com.android.systemui.log.LogBuffer 32 import com.android.systemui.log.core.Logger 33 import com.android.systemui.log.dagger.KeyguardBlueprintLog 34 import javax.inject.Inject 35 import kotlinx.coroutines.flow.MutableStateFlow 36 import kotlinx.coroutines.flow.asStateFlow 37 38 data class TransitionData(val config: Config, val start: Long = System.currentTimeMillis()) 39 40 class KeyguardBlueprintViewModel 41 @Inject 42 constructor( 43 @Main private val handler: Handler, 44 private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, 45 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 46 @KeyguardBlueprintLog private val blueprintLog: LogBuffer, 47 ) { 48 private val logger = Logger(blueprintLog, "KeyguardBlueprintViewModel") 49 val blueprint = keyguardBlueprintInteractor.blueprint 50 val blueprintId = keyguardBlueprintInteractor.blueprintId 51 val refreshTransition = keyguardBlueprintInteractor.refreshTransition 52 53 private val _currentTransition = MutableStateFlow<TransitionData?>(null) 54 val currentTransition = _currentTransition.asStateFlow() 55 56 private val runningTransitions = mutableSetOf<Transition>() 57 private val transitionListener = 58 object : Transition.TransitionListener { onTransitionCancelnull59 override fun onTransitionCancel(transition: Transition) { 60 logger.w({ "onTransitionCancel: $str1" }) { str1 = transition::class.simpleName } 61 updateTransitions(null) { remove(transition) } 62 } 63 onTransitionEndnull64 override fun onTransitionEnd(transition: Transition) { 65 logger.i({ "onTransitionEnd: $str1" }) { str1 = transition::class.simpleName } 66 updateTransitions(null) { remove(transition) } 67 } 68 onTransitionPausenull69 override fun onTransitionPause(transition: Transition) { 70 logger.i({ "onTransitionPause: $str1" }) { str1 = transition::class.simpleName } 71 updateTransitions(null) { remove(transition) } 72 } 73 onTransitionResumenull74 override fun onTransitionResume(transition: Transition) { 75 logger.i({ "onTransitionResume: $str1" }) { str1 = transition::class.simpleName } 76 updateTransitions(null) { add(transition) } 77 } 78 onTransitionStartnull79 override fun onTransitionStart(transition: Transition) { 80 logger.i({ "onTransitionStart: $str1" }) { str1 = transition::class.simpleName } 81 updateTransitions(null) { add(transition) } 82 } 83 } 84 refreshBlueprintnull85 fun refreshBlueprint(type: Type = Type.NoTransition) = 86 keyguardBlueprintInteractor.refreshBlueprint(type) 87 88 fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) { 89 runningTransitions.mutate() 90 91 if (runningTransitions.size <= 0) _currentTransition.value = null 92 else if (data != null) _currentTransition.value = data 93 } 94 runTransitionnull95 fun runTransition( 96 constraintLayout: ConstraintLayout, 97 clockViewModel: KeyguardClockViewModel, 98 smartspaceViewModel: KeyguardSmartspaceViewModel, 99 config: Config, 100 apply: () -> Unit, 101 ) { 102 val newConfig = 103 if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) { 104 config.copy(type = Type.Init) 105 } else { 106 config 107 } 108 109 runTransition( 110 constraintLayout, 111 IntraBlueprintTransition(newConfig, clockViewModel, smartspaceViewModel, blueprintLog), 112 config, 113 apply, 114 ) 115 } 116 runTransitionnull117 fun runTransition( 118 constraintLayout: ConstraintLayout, 119 transition: Transition, 120 config: Config, 121 apply: () -> Unit, 122 ) { 123 val currentPriority = currentTransition.value?.let { it.config.type.priority } ?: -1 124 if (config.checkPriority && config.type.priority < currentPriority) { 125 logger.w({ "runTransition: skipping $str1: currentPriority=$int1; config=$str2" }) { 126 str1 = transition::class.simpleName 127 int1 = currentPriority 128 str2 = "$config" 129 } 130 apply() 131 return 132 } 133 134 // Don't allow transitions with animations while in OFF state 135 val newConfig = 136 if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) { 137 config.copy(type = Type.Init) 138 } else { 139 config 140 } 141 142 logger.i({ "runTransition: running $str1: currentPriority=$int1; config=$str2" }) { 143 str1 = transition::class.simpleName 144 int1 = currentPriority 145 str2 = "$newConfig" 146 } 147 148 // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to 149 // the running set until the copy is started by the handler. 150 updateTransitions(TransitionData(newConfig)) { add(transition) } 151 transition.addListener(transitionListener) 152 153 handler.post { 154 if (newConfig.terminatePrevious) { 155 TransitionManager.endTransitions(constraintLayout) 156 } 157 158 TransitionManager.beginDelayedTransition(constraintLayout, transition) 159 apply() 160 161 // Delay removal until after copied transition has started 162 handler.post { updateTransitions(null) { remove(transition) } } 163 } 164 } 165 } 166