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 android.util.Log
24 import androidx.constraintlayout.widget.ConstraintLayout
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
27 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
28 import javax.inject.Inject
29 import kotlinx.coroutines.flow.MutableStateFlow
30 import kotlinx.coroutines.flow.asStateFlow
31 
32 data class TransitionData(
33     val config: Config,
34     val start: Long = System.currentTimeMillis(),
35 )
36 
37 class KeyguardBlueprintViewModel
38 @Inject
39 constructor(
40     @Main private val handler: Handler,
41     keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
42 ) {
43     val blueprint = keyguardBlueprintInteractor.blueprint
44     val blueprintId = keyguardBlueprintInteractor.blueprintId
45     val refreshTransition = keyguardBlueprintInteractor.refreshTransition
46 
47     private val _currentTransition = MutableStateFlow<TransitionData?>(null)
48     val currentTransition = _currentTransition.asStateFlow()
49 
50     private val runningTransitions = mutableSetOf<Transition>()
51     private val transitionListener =
52         object : Transition.TransitionListener {
onTransitionCancelnull53             override fun onTransitionCancel(transition: Transition) {
54                 if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
55                 updateTransitions(null) { remove(transition) }
56             }
57 
onTransitionEndnull58             override fun onTransitionEnd(transition: Transition) {
59                 if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
60                 updateTransitions(null) { remove(transition) }
61             }
62 
onTransitionPausenull63             override fun onTransitionPause(transition: Transition) {
64                 if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}")
65                 updateTransitions(null) { remove(transition) }
66             }
67 
onTransitionResumenull68             override fun onTransitionResume(transition: Transition) {
69                 if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}")
70                 updateTransitions(null) { add(transition) }
71             }
72 
onTransitionStartnull73             override fun onTransitionStart(transition: Transition) {
74                 if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}")
75                 updateTransitions(null) { add(transition) }
76             }
77         }
78 
updateTransitionsnull79     fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) {
80         runningTransitions.mutate()
81 
82         if (runningTransitions.size <= 0) _currentTransition.value = null
83         else if (data != null) _currentTransition.value = data
84     }
85 
runTransitionnull86     fun runTransition(
87         constraintLayout: ConstraintLayout,
88         transition: Transition,
89         config: Config,
90         apply: () -> Unit,
91     ) {
92         val currentPriority = currentTransition.value?.let { it.config.type.priority } ?: -1
93         if (config.checkPriority && config.type.priority < currentPriority) {
94             if (DEBUG) {
95                 Log.w(
96                     TAG,
97                     "runTransition: skipping ${transition::class.simpleName}: " +
98                         "currentPriority=$currentPriority; config=$config"
99                 )
100             }
101             apply()
102             return
103         }
104 
105         if (DEBUG) {
106             Log.i(
107                 TAG,
108                 "runTransition: running ${transition::class.simpleName}: " +
109                     "currentPriority=$currentPriority; config=$config"
110             )
111         }
112 
113         // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
114         // the running set until the copy is started by the handler.
115         updateTransitions(TransitionData(config)) { add(transition) }
116         transition.addListener(transitionListener)
117 
118         handler.post {
119             if (config.terminatePrevious) {
120                 TransitionManager.endTransitions(constraintLayout)
121             }
122 
123             TransitionManager.beginDelayedTransition(constraintLayout, transition)
124             apply()
125 
126             // Delay removal until after copied transition has started
127             handler.post { updateTransitions(null) { remove(transition) } }
128         }
129     }
130 
131     companion object {
132         private const val TAG = "KeyguardBlueprintViewModel"
133         private const val DEBUG = false
134     }
135 }
136