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.keyguard.domain.interactor
18 
19 import android.animation.ValueAnimator
20 import com.android.app.animation.Interpolators
21 import com.android.systemui.communal.domain.interactor.CommunalInteractor
22 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
23 import com.android.systemui.communal.shared.model.CommunalScenes
24 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
25 import com.android.systemui.keyguard.shared.model.KeyguardState
26 import com.android.systemui.keyguard.shared.model.TransitionInfo
27 import com.android.systemui.keyguard.shared.model.TransitionState
28 import com.android.systemui.scene.shared.flag.SceneContainerFlag
29 import com.android.systemui.util.kotlin.sample
30 import java.util.UUID
31 import javax.inject.Inject
32 
33 class GlanceableHubTransitions
34 @Inject
35 constructor(
36     private val transitionInteractor: KeyguardTransitionInteractor,
37     private val transitionRepository: KeyguardTransitionRepository,
38     private val communalInteractor: CommunalInteractor,
39 ) {
40     /**
41      * Listens for the glanceable hub transition to the specified scene and directly drives the
42      * keyguard transition between the lockscreen and the hub.
43      *
44      * The glanceable hub transition progress is used as the source of truth as it cannot be driven
45      * externally. The progress is used for both transitions caused by user touch input or by
46      * programmatic changes.
47      */
48     suspend fun listenForGlanceableHubTransition(
49         transitionOwnerName: String,
50         fromState: KeyguardState,
51         toState: KeyguardState,
52     ) {
53         // TODO(b/336576536): Check if adaptation for scene framework is needed
54         if (SceneContainerFlag.isEnabled) return
55         val toScene =
56             if (fromState == KeyguardState.GLANCEABLE_HUB) {
57                 CommunalScenes.Blank
58             } else {
59                 CommunalScenes.Communal
60             }
61         var transitionId: UUID? = null
62 
63         communalInteractor
64             .transitionProgressToScene(toScene)
65             .sample(
66                 transitionInteractor.startedKeyguardState,
67                 ::Pair,
68             )
69             .collect { (transitionProgress, lastStartedState) ->
70                 val id = transitionId
71                 if (id == null) {
72                     // No transition started.
73                     if (
74                         transitionProgress is CommunalTransitionProgressModel.Transition &&
75                             lastStartedState == fromState
76                     ) {
77                         transitionId =
78                             transitionRepository.startTransition(
79                                 TransitionInfo(
80                                     ownerName = transitionOwnerName,
81                                     from = fromState,
82                                     to = toState,
83                                     animator = null, // transition will be manually controlled
84                                 )
85                             )
86                     }
87                 } else {
88                     if (lastStartedState != toState) {
89                         return@collect
90                     }
91                     // An existing `id` means a transition is started, and calls to
92                     // `updateTransition` will control it until FINISHED or CANCELED
93                     val nextState: TransitionState
94                     val progressFraction: Float
95                     when (transitionProgress) {
96                         is CommunalTransitionProgressModel.Idle -> {
97                             if (transitionProgress.scene == toScene) {
98                                 nextState = TransitionState.FINISHED
99                                 progressFraction = 1f
100                             } else {
101                                 nextState = TransitionState.CANCELED
102                                 progressFraction = 0f
103                             }
104                         }
105                         is CommunalTransitionProgressModel.Transition -> {
106                             nextState = TransitionState.RUNNING
107                             progressFraction = transitionProgress.progress
108                         }
109                         is CommunalTransitionProgressModel.OtherTransition -> {
110                             // Shouldn't happen but if another transition starts during the
111                             // current one, mark the current one as canceled.
112                             nextState = TransitionState.CANCELED
113                             progressFraction = 0f
114                         }
115                     }
116                     transitionRepository.updateTransition(
117                         id,
118                         progressFraction,
119                         nextState,
120                     )
121 
122                     if (
123                         nextState == TransitionState.CANCELED ||
124                             nextState == TransitionState.FINISHED
125                     ) {
126                         transitionId = null
127                     }
128 
129                     // If canceled, just put the state back.
130                     if (nextState == TransitionState.CANCELED) {
131                         transitionRepository.startTransition(
132                             TransitionInfo(
133                                 ownerName = transitionOwnerName,
134                                 from = toState,
135                                 to = fromState,
136                                 animator =
137                                     ValueAnimator().apply {
138                                         interpolator = Interpolators.LINEAR
139                                         duration = 0
140                                     }
141                             )
142                         )
143                     }
144                 }
145             }
146     }
147 }
148